From f5252922b9d4115b7f8264641c84ef515ea52ea3 Mon Sep 17 00:00:00 2001 From: drewcassidy Date: Wed, 18 Dec 2019 12:53:16 -0800 Subject: [PATCH] Tabs to Spaces --- __init__.py | 2624 ++++++++++++++-------------- op_align.py | 200 +-- op_bake.py | 934 +++++----- op_bake_explode.py | 330 ++-- op_bake_organize_names.py | 270 +-- op_color_assign.py | 158 +- op_color_clear.py | 148 +- op_color_convert_texture.py | 250 +-- op_color_convert_vertex_colors.py | 128 +- op_color_from_directions.py | 380 ++-- op_color_from_elements.py | 160 +- op_color_from_materials.py | 62 +- op_color_io_export.py | 44 +- op_color_io_import.py | 80 +- op_color_select.py | 94 +- op_edge_split_bevel.py | 502 +++--- op_island_align_edge.py | 204 +-- op_island_align_sort.py | 240 +-- op_island_align_world.py | 478 ++--- op_island_mirror.py | 1404 +++++++-------- op_island_rotate_90.py | 110 +- op_island_straighten_edge_loops.py | 398 ++--- op_meshtex_create.py | 498 +++--- op_meshtex_pattern.py | 312 ++-- op_meshtex_trim.py | 84 +- op_meshtex_trim_collapse.py | 80 +- op_meshtex_wrap.py | 130 +- op_rectify.py | 1054 +++++------ op_select_islands_flipped.py | 186 +- op_select_islands_identical.py | 186 +- op_select_islands_outline.py | 92 +- op_select_islands_overlap.py | 188 +- op_smoothing_uv_islands.py | 84 +- op_texel_checker_map.py | 394 ++--- op_texel_density_get.py | 224 +-- op_texel_density_set.py | 342 ++-- op_texture_open.py | 58 +- op_texture_preview.py | 158 +- op_texture_reload_all.py | 90 +- op_texture_remove.py | 36 +- op_texture_save.py | 130 +- op_texture_select.py | 124 +- op_unwrap_edge_peel.py | 144 +- op_unwrap_faces_iron.py | 94 +- op_uv_channel_add.py | 94 +- op_uv_channel_swap.py | 94 +- op_uv_crop.py | 80 +- op_uv_fill.py | 162 +- op_uv_resize.py | 438 ++--- op_uv_size_get.py | 46 +- utilities_bake.py | 882 +++++----- utilities_color.py | 266 +-- utilities_meshtex.py | 180 +- utilities_texel.py | 182 +- utilities_ui.py | 212 +-- utilities_uv.py | 416 ++--- 56 files changed, 8469 insertions(+), 8469 deletions(-) diff --git a/__init__.py b/__init__.py index 4d89e03..bb84d0d 100644 --- a/__init__.py +++ b/__init__.py @@ -1,137 +1,137 @@ bl_info = { - "name": "TexTools", - "description": "Professional UV and Texture tools for Blender.", - "author": "renderhjs, (Port to 2.80 by Sav Martin)", - "version": (1, 3, 00), - "blender": (2, 80, 0), - "category": "UV", - "location": "UV Image Editor > Tools > 'TexTools' panel", - "wiki_url": "http://renderhjs.net/textools/blender/" + "name": "TexTools", + "description": "Professional UV and Texture tools for Blender.", + "author": "renderhjs, (Port to 2.80 by Sav Martin)", + "version": (1, 3, 00), + "blender": (2, 80, 0), + "category": "UV", + "location": "UV Image Editor > Tools > 'TexTools' panel", + "wiki_url": "http://renderhjs.net/textools/blender/" } # Import local modules # More info: https://wiki.blender.org/index.php/Dev:Py/Scripts/Cookbook/Code_snippets/Multi-File_packages if "bpy" in locals(): - import imp - imp.reload(utilities_ui) - imp.reload(settings) - imp.reload(utilities_bake) - imp.reload(utilities_color) - imp.reload(utilities_texel) - imp.reload(utilities_uv) - imp.reload(utilities_meshtex) - - imp.reload(op_align) - imp.reload(op_bake) - imp.reload(op_bake_explode) - imp.reload(op_bake_organize_names) - imp.reload(op_texture_preview) - imp.reload(op_color_assign) - imp.reload(op_color_clear) - imp.reload(op_color_convert_texture) - imp.reload(op_color_convert_vertex_colors) - imp.reload(op_edge_split_bevel) - imp.reload(op_color_from_elements) - imp.reload(op_color_from_materials) - imp.reload(op_color_from_directions) - imp.reload(op_color_io_export) - imp.reload(op_color_io_import) - imp.reload(op_color_select) - imp.reload(op_island_align_edge) - imp.reload(op_island_align_sort) - imp.reload(op_island_align_world) - imp.reload(op_island_mirror) - imp.reload(op_island_rotate_90) - imp.reload(op_island_straighten_edge_loops) - imp.reload(op_rectify) - imp.reload(op_select_islands_identical) - imp.reload(op_select_islands_outline) - imp.reload(op_select_islands_overlap) - imp.reload(op_select_islands_flipped) - imp.reload(op_smoothing_uv_islands) - imp.reload(op_meshtex_create) - imp.reload(op_meshtex_wrap) - imp.reload(op_meshtex_trim) - imp.reload(op_meshtex_trim_collapse) - imp.reload(op_meshtex_pattern) - imp.reload(op_texel_checker_map) - imp.reload(op_texel_density_get) - imp.reload(op_texel_density_set) - imp.reload(op_texture_reload_all) - imp.reload(op_texture_save) - imp.reload(op_texture_open) - imp.reload(op_texture_select) - imp.reload(op_texture_remove) - imp.reload(op_unwrap_faces_iron) - imp.reload(op_unwrap_edge_peel) - imp.reload(op_uv_channel_add) - imp.reload(op_uv_channel_swap) - imp.reload(op_uv_crop) - imp.reload(op_uv_fill) - imp.reload(op_uv_resize) - imp.reload(op_uv_size_get) - - + import imp + imp.reload(utilities_ui) + imp.reload(settings) + imp.reload(utilities_bake) + imp.reload(utilities_color) + imp.reload(utilities_texel) + imp.reload(utilities_uv) + imp.reload(utilities_meshtex) + + imp.reload(op_align) + imp.reload(op_bake) + imp.reload(op_bake_explode) + imp.reload(op_bake_organize_names) + imp.reload(op_texture_preview) + imp.reload(op_color_assign) + imp.reload(op_color_clear) + imp.reload(op_color_convert_texture) + imp.reload(op_color_convert_vertex_colors) + imp.reload(op_edge_split_bevel) + imp.reload(op_color_from_elements) + imp.reload(op_color_from_materials) + imp.reload(op_color_from_directions) + imp.reload(op_color_io_export) + imp.reload(op_color_io_import) + imp.reload(op_color_select) + imp.reload(op_island_align_edge) + imp.reload(op_island_align_sort) + imp.reload(op_island_align_world) + imp.reload(op_island_mirror) + imp.reload(op_island_rotate_90) + imp.reload(op_island_straighten_edge_loops) + imp.reload(op_rectify) + imp.reload(op_select_islands_identical) + imp.reload(op_select_islands_outline) + imp.reload(op_select_islands_overlap) + imp.reload(op_select_islands_flipped) + imp.reload(op_smoothing_uv_islands) + imp.reload(op_meshtex_create) + imp.reload(op_meshtex_wrap) + imp.reload(op_meshtex_trim) + imp.reload(op_meshtex_trim_collapse) + imp.reload(op_meshtex_pattern) + imp.reload(op_texel_checker_map) + imp.reload(op_texel_density_get) + imp.reload(op_texel_density_set) + imp.reload(op_texture_reload_all) + imp.reload(op_texture_save) + imp.reload(op_texture_open) + imp.reload(op_texture_select) + imp.reload(op_texture_remove) + imp.reload(op_unwrap_faces_iron) + imp.reload(op_unwrap_edge_peel) + imp.reload(op_uv_channel_add) + imp.reload(op_uv_channel_swap) + imp.reload(op_uv_crop) + imp.reload(op_uv_fill) + imp.reload(op_uv_resize) + imp.reload(op_uv_size_get) + + else: - from . import settings - from . import utilities_ui - from . import utilities_bake - from . import utilities_color - from . import utilities_texel - from . import utilities_uv - from . import utilities_meshtex - - from . import op_align - from . import op_bake - from . import op_bake_explode - from . import op_bake_organize_names - from . import op_texture_preview - from . import op_color_assign - from . import op_color_clear - from . import op_color_convert_texture - from . import op_color_convert_vertex_colors - from . import op_color_from_elements - from . import op_color_from_materials - from . import op_color_from_directions - from . import op_edge_split_bevel - from . import op_color_io_export - from . import op_color_io_import - from . import op_color_select - from . import op_island_align_edge - from . import op_island_align_sort - from . import op_island_align_world - from . import op_island_mirror - from . import op_island_rotate_90 - from . import op_island_straighten_edge_loops - from . import op_rectify - from . import op_select_islands_identical - from . import op_select_islands_outline - from . import op_select_islands_overlap - from . import op_select_islands_flipped - from . import op_smoothing_uv_islands - from . import op_meshtex_create - from . import op_meshtex_wrap - from . import op_meshtex_trim - from . import op_meshtex_trim_collapse - from . import op_meshtex_pattern - from . import op_texel_checker_map - from . import op_texel_density_get - from . import op_texel_density_set - from . import op_texture_reload_all - from . import op_texture_save - from . import op_texture_open - from . import op_texture_select - from . import op_texture_remove - from . import op_unwrap_faces_iron - from . import op_unwrap_edge_peel - from . import op_uv_channel_add - from . import op_uv_channel_swap - from . import op_uv_crop - from . import op_uv_fill - from . import op_uv_resize - from . import op_uv_size_get - + from . import settings + from . import utilities_ui + from . import utilities_bake + from . import utilities_color + from . import utilities_texel + from . import utilities_uv + from . import utilities_meshtex + + from . import op_align + from . import op_bake + from . import op_bake_explode + from . import op_bake_organize_names + from . import op_texture_preview + from . import op_color_assign + from . import op_color_clear + from . import op_color_convert_texture + from . import op_color_convert_vertex_colors + from . import op_color_from_elements + from . import op_color_from_materials + from . import op_color_from_directions + from . import op_edge_split_bevel + from . import op_color_io_export + from . import op_color_io_import + from . import op_color_select + from . import op_island_align_edge + from . import op_island_align_sort + from . import op_island_align_world + from . import op_island_mirror + from . import op_island_rotate_90 + from . import op_island_straighten_edge_loops + from . import op_rectify + from . import op_select_islands_identical + from . import op_select_islands_outline + from . import op_select_islands_overlap + from . import op_select_islands_flipped + from . import op_smoothing_uv_islands + from . import op_meshtex_create + from . import op_meshtex_wrap + from . import op_meshtex_trim + from . import op_meshtex_trim_collapse + from . import op_meshtex_pattern + from . import op_texel_checker_map + from . import op_texel_density_get + from . import op_texel_density_set + from . import op_texture_reload_all + from . import op_texture_save + from . import op_texture_open + from . import op_texture_select + from . import op_texture_remove + from . import op_unwrap_faces_iron + from . import op_unwrap_edge_peel + from . import op_uv_channel_add + from . import op_uv_channel_swap + from . import op_uv_crop + from . import op_uv_fill + from . import op_uv_resize + from . import op_uv_size_get + # Import general modules. Important: must be placed here and not on top import bpy @@ -143,1129 +143,1129 @@ import bpy.utils.previews from bpy.types import Menu, Operator, Panel, UIList from bpy.props import ( - StringProperty, - BoolProperty, - IntProperty, - FloatProperty, - FloatVectorProperty, - EnumProperty, - PointerProperty, + StringProperty, + BoolProperty, + IntProperty, + FloatProperty, + FloatVectorProperty, + EnumProperty, + PointerProperty, ) class Panel_Preferences(bpy.types.AddonPreferences): - bl_idname = __name__ - - # Addon Preferences https://docs.blender.org/api/blender_python_api_2_67_release/bpy.types.AddonPreferences.html - swizzle_y_coordinate : bpy.props.EnumProperty(items= - [ - ('Y+', 'Y+ OpenGL', 'Used in Blender, Maya, Modo, Toolbag, Unity'), - ('Y-', 'Y- Direct X', 'Used in 3ds Max, CryENGINE, Source, Unreal Engine') - ], - description="Color template", - name = "Swizzle Coordinates", - default = 'Y+' - ) - bake_32bit_float : bpy.props.EnumProperty(items= - [ - ('8', '8 Bit', ''), - ('32', '32 Bit', '') - ], - description="", - name = "Image depth", - default = '8' - ) - - def draw(self, context): - layout = self.layout - - box = layout.box() - col = box.column(align=True) - col.prop(self, "swizzle_y_coordinate", icon='ORIENTATION_GLOBAL') - if self.swizzle_y_coordinate == 'Y+': - col.label(text="Y+ used in: Blender, Maya, Modo, Toolbag, Unity") - elif self.swizzle_y_coordinate == 'Y-': - col.label(text="Y- used in: 3ds Max, CryENGINE, Source, Unreal Engine") - - box.separator() - col = box.column(align=True) - col.prop(self, "bake_32bit_float", icon='IMAGE_RGB') - if self.bake_32bit_float == '8': - col.label(text="8 Bit images are used. Banding may appear in normal maps.") - elif self.bake_32bit_float == '32': - col.label(text="32 Bit images are used. Images may require dithering to 8 bit.") - - - if not hasattr(bpy.types,"ShaderNodeBevel"): - box.separator() - col = box.column(align=True) - - col.label(text="Unlock Bevel Shader", icon='ERROR') - col.operator("wm.url_open", text="Get Blender with Bevel Shader", icon='BLENDER').url = "https://builder.blender.org/download/" - col.label(text="Use nightly builds of Blender 2.79 or 2.8 to access Bevel baking") - - - - box = layout.box() - - box.label(text="Additional Links") - col = box.column(align=True) - col.operator("wm.url_open", text="Donate", icon='HELP').url = "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=ZC9X4LE7CPQN6" - col.operator("wm.url_open", text="GIT Code", icon='WORDWRAP_ON').url = "https://bitbucket.org/renderhjs/textools-blender/src" - - col.label(text="Discussions") - row = col.row(align=True) - row.operator("wm.url_open", text="BlenderArtists", icon='BLENDER').url = "https://blenderartists.org/forum/showthread.php?443182-TexTools-for-Blender" - row.operator("wm.url_open", text="Polycount").url = "http://polycount.com/discussion/197226/textools-for-blender" - row.operator("wm.url_open", text="Twitter").url = "https://twitter.com/search?q=%23textools" - + bl_idname = __name__ + + # Addon Preferences https://docs.blender.org/api/blender_python_api_2_67_release/bpy.types.AddonPreferences.html + swizzle_y_coordinate : bpy.props.EnumProperty(items= + [ + ('Y+', 'Y+ OpenGL', 'Used in Blender, Maya, Modo, Toolbag, Unity'), + ('Y-', 'Y- Direct X', 'Used in 3ds Max, CryENGINE, Source, Unreal Engine') + ], + description="Color template", + name = "Swizzle Coordinates", + default = 'Y+' + ) + bake_32bit_float : bpy.props.EnumProperty(items= + [ + ('8', '8 Bit', ''), + ('32', '32 Bit', '') + ], + description="", + name = "Image depth", + default = '8' + ) + + def draw(self, context): + layout = self.layout + + box = layout.box() + col = box.column(align=True) + col.prop(self, "swizzle_y_coordinate", icon='ORIENTATION_GLOBAL') + if self.swizzle_y_coordinate == 'Y+': + col.label(text="Y+ used in: Blender, Maya, Modo, Toolbag, Unity") + elif self.swizzle_y_coordinate == 'Y-': + col.label(text="Y- used in: 3ds Max, CryENGINE, Source, Unreal Engine") + + box.separator() + col = box.column(align=True) + col.prop(self, "bake_32bit_float", icon='IMAGE_RGB') + if self.bake_32bit_float == '8': + col.label(text="8 Bit images are used. Banding may appear in normal maps.") + elif self.bake_32bit_float == '32': + col.label(text="32 Bit images are used. Images may require dithering to 8 bit.") + + + if not hasattr(bpy.types,"ShaderNodeBevel"): + box.separator() + col = box.column(align=True) + + col.label(text="Unlock Bevel Shader", icon='ERROR') + col.operator("wm.url_open", text="Get Blender with Bevel Shader", icon='BLENDER').url = "https://builder.blender.org/download/" + col.label(text="Use nightly builds of Blender 2.79 or 2.8 to access Bevel baking") + + + + box = layout.box() + + box.label(text="Additional Links") + col = box.column(align=True) + col.operator("wm.url_open", text="Donate", icon='HELP').url = "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=ZC9X4LE7CPQN6" + col.operator("wm.url_open", text="GIT Code", icon='WORDWRAP_ON').url = "https://bitbucket.org/renderhjs/textools-blender/src" + + col.label(text="Discussions") + row = col.row(align=True) + row.operator("wm.url_open", text="BlenderArtists", icon='BLENDER').url = "https://blenderartists.org/forum/showthread.php?443182-TexTools-for-Blender" + row.operator("wm.url_open", text="Polycount").url = "http://polycount.com/discussion/197226/textools-for-blender" + row.operator("wm.url_open", text="Twitter").url = "https://twitter.com/search?q=%23textools" + class UV_OT_op_debug(bpy.types.Operator): - bl_idname = "uv.op_debug" - bl_label = "Debug" - bl_description = "Open console and enable dbug mode" + bl_idname = "uv.op_debug" + bl_label = "Debug" + bl_description = "Open console and enable dbug mode" - @classmethod - def poll(cls, context): - return True + @classmethod + def poll(cls, context): + return True - def execute(self, context): - bpy.app.debug = True# Debug Vertex indexies - bpy.context.object.data.show_extra_indices = True - bpy.app.debug_value = 1 #Set to Non '0 - return {'FINISHED'} + def execute(self, context): + bpy.app.debug = True# Debug Vertex indexies + bpy.context.object.data.show_extra_indices = True + bpy.app.debug_value = 1 #Set to Non '0 + return {'FINISHED'} class UV_OT_op_disable_uv_sync(bpy.types.Operator): - bl_idname = "uv.op_disable_sync" - bl_label = "Disable Sync" - bl_description = "Disable UV sync mode" + bl_idname = "uv.op_disable_sync" + bl_label = "Disable Sync" + bl_description = "Disable UV sync mode" - @classmethod - def poll(cls, context): - return True + @classmethod + def poll(cls, context): + return True - def execute(self, context): - bpy.context.scene.tool_settings.use_uv_select_sync = False - bpy.ops.mesh.select_all(action='SELECT') - return {'FINISHED'} + def execute(self, context): + bpy.context.scene.tool_settings.use_uv_select_sync = False + bpy.ops.mesh.select_all(action='SELECT') + return {'FINISHED'} class UV_OT_op_select_bake_set(bpy.types.Operator): - bl_idname = "uv.op_select_bake_set" - bl_label = "Select" - bl_description = "Select this bake set in scene" - - select_set : bpy.props.StringProperty(default="") - - @classmethod - def poll(cls, context): - return True - - def execute(self, context): - print("Set: "+self.select_set) - if self.select_set != "": - for set in settings.sets: - if set.name == self.select_set: - # Select this entire set - bpy.ops.object.select_all(action='DESELECT') - for obj in set.objects_low: - obj.select_set( state = True, view_layer = None) - for obj in set.objects_high: - obj.select_set( state = True, view_layer = None) - for obj in set.objects_cage: - obj.select_set( state = True, view_layer = None) - # Set active object to low poly to better visualize high and low wireframe color - if len(set.objects_low) > 0: - bpy.context.view_layer.objects.active = set.objects_low[0] - - break - return {'FINISHED'} + bl_idname = "uv.op_select_bake_set" + bl_label = "Select" + bl_description = "Select this bake set in scene" + + select_set : bpy.props.StringProperty(default="") + + @classmethod + def poll(cls, context): + return True + + def execute(self, context): + print("Set: "+self.select_set) + if self.select_set != "": + for set in settings.sets: + if set.name == self.select_set: + # Select this entire set + bpy.ops.object.select_all(action='DESELECT') + for obj in set.objects_low: + obj.select_set( state = True, view_layer = None) + for obj in set.objects_high: + obj.select_set( state = True, view_layer = None) + for obj in set.objects_cage: + obj.select_set( state = True, view_layer = None) + # Set active object to low poly to better visualize high and low wireframe color + if len(set.objects_low) > 0: + bpy.context.view_layer.objects.active = set.objects_low[0] + + break + return {'FINISHED'} class UV_OT_op_select_bake_type(bpy.types.Operator): - bl_idname = "uv.op_select_bake_type" - bl_label = "Select" - bl_description = "Select bake objects of this type" + bl_idname = "uv.op_select_bake_type" + bl_label = "Select" + bl_description = "Select bake objects of this type" - select_type : bpy.props.StringProperty(default='low') + select_type : bpy.props.StringProperty(default='low') - @classmethod - def poll(cls, context): - return True + @classmethod + def poll(cls, context): + return True - def execute(self, context): - objects = [] - for set in settings.sets: - if self.select_type == 'low': - objects+=set.objects_low - elif self.select_type == 'high': - objects+=set.objects_high - elif self.select_type == 'cage': - objects+=set.objects_cage - elif self.select_type == 'float': - objects+=set.objects_float - elif self.select_type == 'issue' and set.has_issues: - objects+=set.objects_low - objects+=set.objects_high - objects+=set.objects_cage - objects+=set.objects_float + def execute(self, context): + objects = [] + for set in settings.sets: + if self.select_type == 'low': + objects+=set.objects_low + elif self.select_type == 'high': + objects+=set.objects_high + elif self.select_type == 'cage': + objects+=set.objects_cage + elif self.select_type == 'float': + objects+=set.objects_float + elif self.select_type == 'issue' and set.has_issues: + objects+=set.objects_low + objects+=set.objects_high + objects+=set.objects_cage + objects+=set.objects_float - bpy.ops.object.select_all(action='DESELECT') - for obj in objects: - obj.select_set( state = True, view_layer = None) + bpy.ops.object.select_all(action='DESELECT') + for obj in objects: + obj.select_set( state = True, view_layer = None) - return {'FINISHED'} + return {'FINISHED'} def on_dropdown_size(self, context): - # Help: http://elfnor.com/drop-down-and-button-select-menus-for-blender-operator-add-ons.html - size = int(bpy.context.scene.texToolsSettings.size_dropdown) - bpy.context.scene.texToolsSettings.size[0] = size; - bpy.context.scene.texToolsSettings.size[1] = size; + # Help: http://elfnor.com/drop-down-and-button-select-menus-for-blender-operator-add-ons.html + size = int(bpy.context.scene.texToolsSettings.size_dropdown) + bpy.context.scene.texToolsSettings.size[0] = size; + bpy.context.scene.texToolsSettings.size[1] = size; - if size <= 128: - bpy.context.scene.texToolsSettings.padding = 2 - elif size <= 512: - bpy.context.scene.texToolsSettings.padding = 4 - else: - bpy.context.scene.texToolsSettings.padding = 8 + if size <= 128: + bpy.context.scene.texToolsSettings.padding = 2 + elif size <= 512: + bpy.context.scene.texToolsSettings.padding = 4 + else: + bpy.context.scene.texToolsSettings.padding = 8 def on_dropdown_uv_channel(self, context): - if bpy.context.active_object != None: - if bpy.context.active_object.type == 'MESH': - if bpy.context.object.data.uv_layers: + if bpy.context.active_object != None: + if bpy.context.active_object.type == 'MESH': + if bpy.context.object.data.uv_layers: - # Change Mesh UV Channel - index = int(bpy.context.scene.texToolsSettings.uv_channel) - if index < len(bpy.context.object.data.uv_layers): - bpy.context.object.data.uv_layers.active_index = index - bpy.context.object.data.uv_layers[index].active_render = True + # Change Mesh UV Channel + index = int(bpy.context.scene.texToolsSettings.uv_channel) + if index < len(bpy.context.object.data.uv_layers): + bpy.context.object.data.uv_layers.active_index = index + bpy.context.object.data.uv_layers[index].active_render = True def on_color_changed(self, context): - for i in range(0, context.scene.texToolsSettings.color_ID_count): - utilities_color.assign_color(i) + for i in range(0, context.scene.texToolsSettings.color_ID_count): + utilities_color.assign_color(i) def on_color_dropdown_template(self, context): - # Change Mesh UV Channel - hex_colors = bpy.context.scene.texToolsSettings.color_ID_templates.split(',') - bpy.context.scene.texToolsSettings.color_ID_count = len(hex_colors) + # Change Mesh UV Channel + hex_colors = bpy.context.scene.texToolsSettings.color_ID_templates.split(',') + bpy.context.scene.texToolsSettings.color_ID_count = len(hex_colors) - # Assign color slots - for i in range(0, len(hex_colors)): - color = utilities_color.hex_to_color("#"+hex_colors[i]) - utilities_color.set_color(i, color) - utilities_color.assign_color(i) + # Assign color slots + for i in range(0, len(hex_colors)): + color = utilities_color.hex_to_color("#"+hex_colors[i]) + utilities_color.set_color(i, color) + utilities_color.assign_color(i) def on_color_count_changed(self, context): - if bpy.context.active_object != None: - utilities_color.validate_face_colors(bpy.context.active_object) + if bpy.context.active_object != None: + utilities_color.validate_face_colors(bpy.context.active_object) def get_dropdown_uv_values(self, context): - # Requires mesh and UV data - if bpy.context.active_object != None: - if bpy.context.active_object.type == 'MESH': - if bpy.context.object.data.uv_layers: - options = [] - step = 0 - for uvLoop in bpy.context.object.data.uv_layers: - # options.append((str(step), "#{} {}".format(step+1, uvLoop.name), "Change UV channel to '{}'".format(uvLoop.name), step)) - options.append((str(step), "UV {}".format(step+1), "Change UV channel to '{}'".format(uvLoop.name), step)) - step+=1 + # Requires mesh and UV data + if bpy.context.active_object != None: + if bpy.context.active_object.type == 'MESH': + if bpy.context.object.data.uv_layers: + options = [] + step = 0 + for uvLoop in bpy.context.object.data.uv_layers: + # options.append((str(step), "#{} {}".format(step+1, uvLoop.name), "Change UV channel to '{}'".format(uvLoop.name), step)) + options.append((str(step), "UV {}".format(step+1), "Change UV channel to '{}'".format(uvLoop.name), step)) + step+=1 - return options - return [] + return options + return [] def on_slider_meshtexture_wrap(self, context): - value = bpy.context.scene.texToolsSettings.meshtexture_wrap - obj_uv = utilities_meshtex.find_uv_mesh(bpy.context.selected_objects) - if obj_uv: - obj_uv.data.shape_keys.key_blocks["model"].value = value + value = bpy.context.scene.texToolsSettings.meshtexture_wrap + obj_uv = utilities_meshtex.find_uv_mesh(bpy.context.selected_objects) + if obj_uv: + obj_uv.data.shape_keys.key_blocks["model"].value = value class TexToolsSettings(bpy.types.PropertyGroup): - #Width and Height - size : bpy.props.IntVectorProperty( - name = "Size", - size=2, - description="Texture & UV size in pixels", - default = (512,512), - subtype = "XYZ" - ) - size_dropdown : bpy.props.EnumProperty( - items = utilities_ui.size_textures, - name = "Texture Size", - update = on_dropdown_size, - default = '512' - ) - uv_channel : bpy.props.EnumProperty( - items = get_dropdown_uv_values, - name = "UV", - update = on_dropdown_uv_channel - ) - padding : bpy.props.IntProperty( - name = "Padding", - description="padding size in pixels", - default = 4, - min = 0, - max = 256 - ) - bake_samples : bpy.props.FloatProperty( - name = "Samples", - description = "Samples in Cycles for Baking. The higher the less noise. Default: 64", - default = 8, - min = 1, - max = 4000 - ) - bake_curvature_size : bpy.props.IntProperty( - name = "Curvature", - description = "Curvature offset in pixels to process", - default = 1, - min = 1, - max = 64 - ) - bake_wireframe_size : bpy.props.FloatProperty( - name = "Thickness", - description = "Wireframe Thickness in pixels", - default = 1, - min = 0.1, - max = 4.0 - ) - bake_bevel_size : bpy.props.FloatProperty( - name = "Radius", - description = "Bevel radius 1 to 16", - default = 0.05, - min = 0.0, - max = 1.0 - ) - bake_bevel_samples : bpy.props.IntProperty( - name = "Bevel Samples", - description = "Bevel Samples", - default = 4, - min = 1, - max = 16 - ) - - - bake_ray_distance : bpy.props.FloatProperty( - name = "Ray Dist.", - description = "Ray distance when baking. When using cage used as extrude distance", - default = 0.01, - min = 0.000, - max = 100.00 - ) - bake_force_single : bpy.props.BoolProperty( - name="Single Texture", - description="Force a single texture bake accross all selected objects", - default = False - ) - bake_sampling : bpy.props.EnumProperty(items= - [('1', 'None', 'No Anti Aliasing (Fast)'), - ('2', '2x', 'Render 2x and downsample'), - ('4', '4x', 'Render 2x and downsample')], name = "AA", default = '1' - ) - bake_freeze_selection : bpy.props.BoolProperty( - name="Lock", - description="Lock baking sets, don't change with selection", - default = False - ) - texel_mode_scale : bpy.props.EnumProperty(items= - [('ISLAND', 'Islands', 'Scale UV islands to match Texel Density'), - ('ALL', 'Combined', 'Scale all UVs together to match Texel Density')], - name = "Mode", - default = 'ISLAND' - ) - texel_density : bpy.props.FloatProperty( - name = "Texel", - description = "Texel size or Pixels per 1 unit ratio", - default = 256, - min = 0.0 - # max = 100.00 - ) - meshtexture_wrap : bpy.props.FloatProperty( - name = "Wrap", - description = "Transition of mesh texture wrap", - default = 0, - min = 0, - max = 1, - update = on_slider_meshtexture_wrap, - subtype = 'FACTOR' - ) - - def get_color(hex = "808080"): - return bpy.props.FloatVectorProperty( - name="Color1", - description="Set Color 1 for the Palette", - subtype="COLOR", - default=utilities_color.hex_to_color(hex), - size=3, - max=1.0, min=0.0, - update=on_color_changed - )#, update=update_color_1 - - # 10 Color ID's - color_ID_color_0 : get_color(hex="#ff0000") - color_ID_color_1 : get_color(hex="#0000ff") - color_ID_color_2 : get_color(hex="#00ff00") - color_ID_color_3 : get_color(hex="#ffff00") - color_ID_color_4 : get_color(hex="#00ffff") - color_ID_color_5 : get_color() - color_ID_color_6 : get_color() - color_ID_color_7 : get_color() - color_ID_color_8 : get_color() - color_ID_color_9 : get_color() - color_ID_color_10 : get_color() - color_ID_color_11 : get_color() - color_ID_color_12 : get_color() - color_ID_color_13 : get_color() - color_ID_color_14 : get_color() - color_ID_color_15 : get_color() - color_ID_color_16 : get_color() - color_ID_color_17 : get_color() - color_ID_color_18 : get_color() - color_ID_color_19 : get_color() - - color_ID_templates : bpy.props.EnumProperty(items= - [ - ('3d3d3d,7f7f7f,b8b8b8,ffffff', '4 Gray', '...'), - ('003153,345d4b,688a42,9db63a,d1e231', '5 Greens', '...'), - ('ff0000,0000ff,00ff00,ffff00,00ffff', '5 Code', '...'), - ('3a4342,2e302f,242325,d5cc9e,d6412b', '5 Sea Wolf', '...'), - ('7f87a0,2d3449,000000,ffffff,f99c21', '5 Mustang', '...'), - ('143240,209d8c,fed761,ffab56,fb6941', '5 Sunset', '...'), - ('0087ed,23ca7a,eceb1d,e37a29,da1c2c', '5 Heat', '...'), - ('9e00af,7026b9,4f44b5,478bf4,39b7d5,229587,47b151,9dcf46,f7f235,f7b824,f95f1e,c5513c,78574a,4d4b4b,9d9d9d', '15 Rainbow', '...') - ], - description="Color template", - name = "Preset", - update = on_color_dropdown_template, - default = 'ff0000,0000ff,00ff00,ffff00,00ffff' - ) - - color_ID_count : bpy.props.IntProperty( - name = "Count", - description="Number of color IDs", - default = 5, - update = on_color_count_changed, - min = 2, - max = 20 - ) - - # bake_do_save = bpy.props.BoolProperty( - # name="Save", - # description="Save the baked texture?", - # default = False) + #Width and Height + size : bpy.props.IntVectorProperty( + name = "Size", + size=2, + description="Texture & UV size in pixels", + default = (512,512), + subtype = "XYZ" + ) + size_dropdown : bpy.props.EnumProperty( + items = utilities_ui.size_textures, + name = "Texture Size", + update = on_dropdown_size, + default = '512' + ) + uv_channel : bpy.props.EnumProperty( + items = get_dropdown_uv_values, + name = "UV", + update = on_dropdown_uv_channel + ) + padding : bpy.props.IntProperty( + name = "Padding", + description="padding size in pixels", + default = 4, + min = 0, + max = 256 + ) + bake_samples : bpy.props.FloatProperty( + name = "Samples", + description = "Samples in Cycles for Baking. The higher the less noise. Default: 64", + default = 8, + min = 1, + max = 4000 + ) + bake_curvature_size : bpy.props.IntProperty( + name = "Curvature", + description = "Curvature offset in pixels to process", + default = 1, + min = 1, + max = 64 + ) + bake_wireframe_size : bpy.props.FloatProperty( + name = "Thickness", + description = "Wireframe Thickness in pixels", + default = 1, + min = 0.1, + max = 4.0 + ) + bake_bevel_size : bpy.props.FloatProperty( + name = "Radius", + description = "Bevel radius 1 to 16", + default = 0.05, + min = 0.0, + max = 1.0 + ) + bake_bevel_samples : bpy.props.IntProperty( + name = "Bevel Samples", + description = "Bevel Samples", + default = 4, + min = 1, + max = 16 + ) + + + bake_ray_distance : bpy.props.FloatProperty( + name = "Ray Dist.", + description = "Ray distance when baking. When using cage used as extrude distance", + default = 0.01, + min = 0.000, + max = 100.00 + ) + bake_force_single : bpy.props.BoolProperty( + name="Single Texture", + description="Force a single texture bake accross all selected objects", + default = False + ) + bake_sampling : bpy.props.EnumProperty(items= + [('1', 'None', 'No Anti Aliasing (Fast)'), + ('2', '2x', 'Render 2x and downsample'), + ('4', '4x', 'Render 2x and downsample')], name = "AA", default = '1' + ) + bake_freeze_selection : bpy.props.BoolProperty( + name="Lock", + description="Lock baking sets, don't change with selection", + default = False + ) + texel_mode_scale : bpy.props.EnumProperty(items= + [('ISLAND', 'Islands', 'Scale UV islands to match Texel Density'), + ('ALL', 'Combined', 'Scale all UVs together to match Texel Density')], + name = "Mode", + default = 'ISLAND' + ) + texel_density : bpy.props.FloatProperty( + name = "Texel", + description = "Texel size or Pixels per 1 unit ratio", + default = 256, + min = 0.0 + # max = 100.00 + ) + meshtexture_wrap : bpy.props.FloatProperty( + name = "Wrap", + description = "Transition of mesh texture wrap", + default = 0, + min = 0, + max = 1, + update = on_slider_meshtexture_wrap, + subtype = 'FACTOR' + ) + + def get_color(hex = "808080"): + return bpy.props.FloatVectorProperty( + name="Color1", + description="Set Color 1 for the Palette", + subtype="COLOR", + default=utilities_color.hex_to_color(hex), + size=3, + max=1.0, min=0.0, + update=on_color_changed + )#, update=update_color_1 + + # 10 Color ID's + color_ID_color_0 : get_color(hex="#ff0000") + color_ID_color_1 : get_color(hex="#0000ff") + color_ID_color_2 : get_color(hex="#00ff00") + color_ID_color_3 : get_color(hex="#ffff00") + color_ID_color_4 : get_color(hex="#00ffff") + color_ID_color_5 : get_color() + color_ID_color_6 : get_color() + color_ID_color_7 : get_color() + color_ID_color_8 : get_color() + color_ID_color_9 : get_color() + color_ID_color_10 : get_color() + color_ID_color_11 : get_color() + color_ID_color_12 : get_color() + color_ID_color_13 : get_color() + color_ID_color_14 : get_color() + color_ID_color_15 : get_color() + color_ID_color_16 : get_color() + color_ID_color_17 : get_color() + color_ID_color_18 : get_color() + color_ID_color_19 : get_color() + + color_ID_templates : bpy.props.EnumProperty(items= + [ + ('3d3d3d,7f7f7f,b8b8b8,ffffff', '4 Gray', '...'), + ('003153,345d4b,688a42,9db63a,d1e231', '5 Greens', '...'), + ('ff0000,0000ff,00ff00,ffff00,00ffff', '5 Code', '...'), + ('3a4342,2e302f,242325,d5cc9e,d6412b', '5 Sea Wolf', '...'), + ('7f87a0,2d3449,000000,ffffff,f99c21', '5 Mustang', '...'), + ('143240,209d8c,fed761,ffab56,fb6941', '5 Sunset', '...'), + ('0087ed,23ca7a,eceb1d,e37a29,da1c2c', '5 Heat', '...'), + ('9e00af,7026b9,4f44b5,478bf4,39b7d5,229587,47b151,9dcf46,f7f235,f7b824,f95f1e,c5513c,78574a,4d4b4b,9d9d9d', '15 Rainbow', '...') + ], + description="Color template", + name = "Preset", + update = on_color_dropdown_template, + default = 'ff0000,0000ff,00ff00,ffff00,00ffff' + ) + + color_ID_count : bpy.props.IntProperty( + name = "Count", + description="Number of color IDs", + default = 5, + update = on_color_count_changed, + min = 2, + max = 20 + ) + + # bake_do_save = bpy.props.BoolProperty( + # name="Save", + # description="Save the baked texture?", + # default = False) class UI_PT_Panel_Units(bpy.types.Panel): - bl_label = " " - bl_space_type = 'IMAGE_EDITOR' - bl_region_type = 'UI' - bl_category = "TexTools" - #bl_options = {'HIDE_HEADER'} - - def draw_header(self, _): - layout = self.layout - row = layout.row(align=True) - row.label(text ="TexTools") - #layout.label(text="Size: {} x {}".format(bpy.context.scene.texToolsSettings.size[0], bpy.context.scene.texToolsSettings.size[1])) - - def draw(self, context): - layout = self.layout - - if bpy.app.debug_value != 0: - row = layout.row() - row.alert =True - row.operator("uv.op_debug", text="DEBUG", icon="CONSOLE") - - #---------- Settings ------------ - # row = layout.row() - col = layout.column(align=True) - r = col.row(align = True) - r.prop(context.scene.texToolsSettings, "size_dropdown", text="Size") - r.operator(op_uv_size_get.op.bl_idname, text="", icon = 'EYEDROPPER') - - r = col.row(align = True) - r.prop(context.scene.texToolsSettings, "size", text="") - - r = col.row(align = True) - r.prop(context.scene.texToolsSettings, "padding", text="Padding") - r.operator(op_uv_resize.op.bl_idname, text="Resize", icon_value = icon_get("op_extend_canvas_open")) - - - # col.operator(op_extend_canvas.op.bl_idname, text="Resize", icon_value = icon_get("op_extend_canvas")) - - - # UV Channel - - row = layout.row() - - has_uv_channel = False - if bpy.context.active_object and len(bpy.context.selected_objects) == 1: - if bpy.context.active_object in bpy.context.selected_objects: - if bpy.context.active_object.type == 'MESH': - - # split = row.split(percentage=0.25) - # c = row.column(align=True) - # r = row.row(align=True) - # r.alignment = 'RIGHT' - # r.expand = - # row.label(text="UV")#, icon='GROUP_UVS' - - - if not bpy.context.object.data.uv_layers: - # c = split.column(align=True) - # row = c.row(align=True) - # row.label(text="None", icon= 'ERROR') - - row.operator(op_uv_channel_add.op.bl_idname, text="Add", icon = 'REMOVE') - else: - # c = split.column(align=True) - # row = c.row(align=True) - group = row.row(align=True) - group.prop(context.scene.texToolsSettings, "uv_channel", text="") - group.operator(op_uv_channel_add.op.bl_idname, text="", icon = 'ADD') - - # c = split.column(align=True) - # row = c.row(align=True) - # row.alignment = 'RIGHT' - group = row.row(align=True) - r = group.column(align=True) - r.active = bpy.context.object.data.uv_layers.active_index > 0 - r.operator(op_uv_channel_swap.op.bl_idname, text="", icon = 'TRIA_UP_BAR').is_down = False; - - r = group.column(align=True) - r.active = bpy.context.object.data.uv_layers.active_index < (len(bpy.context.object.data.uv_layers)-1) - r.operator(op_uv_channel_swap.op.bl_idname, text="", icon = 'TRIA_DOWN_BAR').is_down = True; - - has_uv_channel = True - if not has_uv_channel: - row.label(text="UV") - - - col = layout.column(align=True) - - # col.separator() - col.operator(op_texture_reload_all.op.bl_idname, text="Reload Textures", icon_value = icon_get("op_texture_reload_all")) - - row = col.row(align=True) - row.scale_y = 1.75 - row.operator(op_texel_checker_map.op.bl_idname, text ="Checker Map", icon_value = icon_get("op_texel_checker_map")) - - - - + bl_label = " " + bl_space_type = 'IMAGE_EDITOR' + bl_region_type = 'UI' + bl_category = "TexTools" + #bl_options = {'HIDE_HEADER'} + + def draw_header(self, _): + layout = self.layout + row = layout.row(align=True) + row.label(text ="TexTools") + #layout.label(text="Size: {} x {}".format(bpy.context.scene.texToolsSettings.size[0], bpy.context.scene.texToolsSettings.size[1])) + + def draw(self, context): + layout = self.layout + + if bpy.app.debug_value != 0: + row = layout.row() + row.alert =True + row.operator("uv.op_debug", text="DEBUG", icon="CONSOLE") + + #---------- Settings ------------ + # row = layout.row() + col = layout.column(align=True) + r = col.row(align = True) + r.prop(context.scene.texToolsSettings, "size_dropdown", text="Size") + r.operator(op_uv_size_get.op.bl_idname, text="", icon = 'EYEDROPPER') + + r = col.row(align = True) + r.prop(context.scene.texToolsSettings, "size", text="") + + r = col.row(align = True) + r.prop(context.scene.texToolsSettings, "padding", text="Padding") + r.operator(op_uv_resize.op.bl_idname, text="Resize", icon_value = icon_get("op_extend_canvas_open")) + + + # col.operator(op_extend_canvas.op.bl_idname, text="Resize", icon_value = icon_get("op_extend_canvas")) + + + # UV Channel + + row = layout.row() + + has_uv_channel = False + if bpy.context.active_object and len(bpy.context.selected_objects) == 1: + if bpy.context.active_object in bpy.context.selected_objects: + if bpy.context.active_object.type == 'MESH': + + # split = row.split(percentage=0.25) + # c = row.column(align=True) + # r = row.row(align=True) + # r.alignment = 'RIGHT' + # r.expand = + # row.label(text="UV")#, icon='GROUP_UVS' + + + if not bpy.context.object.data.uv_layers: + # c = split.column(align=True) + # row = c.row(align=True) + # row.label(text="None", icon= 'ERROR') + + row.operator(op_uv_channel_add.op.bl_idname, text="Add", icon = 'REMOVE') + else: + # c = split.column(align=True) + # row = c.row(align=True) + group = row.row(align=True) + group.prop(context.scene.texToolsSettings, "uv_channel", text="") + group.operator(op_uv_channel_add.op.bl_idname, text="", icon = 'ADD') + + # c = split.column(align=True) + # row = c.row(align=True) + # row.alignment = 'RIGHT' + group = row.row(align=True) + r = group.column(align=True) + r.active = bpy.context.object.data.uv_layers.active_index > 0 + r.operator(op_uv_channel_swap.op.bl_idname, text="", icon = 'TRIA_UP_BAR').is_down = False; + + r = group.column(align=True) + r.active = bpy.context.object.data.uv_layers.active_index < (len(bpy.context.object.data.uv_layers)-1) + r.operator(op_uv_channel_swap.op.bl_idname, text="", icon = 'TRIA_DOWN_BAR').is_down = True; + + has_uv_channel = True + if not has_uv_channel: + row.label(text="UV") + + + col = layout.column(align=True) + + # col.separator() + col.operator(op_texture_reload_all.op.bl_idname, text="Reload Textures", icon_value = icon_get("op_texture_reload_all")) + + row = col.row(align=True) + row.scale_y = 1.75 + row.operator(op_texel_checker_map.op.bl_idname, text ="Checker Map", icon_value = icon_get("op_texel_checker_map")) + + + + class UI_PT_Panel_Layout(bpy.types.Panel): - bl_label = " " - bl_space_type = 'IMAGE_EDITOR' - bl_region_type = 'UI' - bl_category = "TexTools" - bl_options = {'DEFAULT_CLOSED'} - - def draw_header(self, _): - layout = self.layout - row = layout.row(align=True) - row.operator("wm.url_open", text="", icon='INFO').url = "http://renderhjs.net/textools/blender/index.html#uvlayout" - row.label(text ="UV Layout") - - # def draw_header(self, _): - # layout = self.layout - # layout.label(text="", icon_value=icon("logo")) - - - def draw(self, context): - layout = self.layout - - - if bpy.app.debug_value != 0: - col = layout.column(align=True) - col.alert = True - row = col.row(align=True) - row.operator(op_island_mirror.op.bl_idname, text="Mirror", icon_value = icon_get("op_island_mirror")).is_stack = False; - row.operator(op_island_mirror.op.bl_idname, text="Stack", icon_value = icon_get("op_island_mirror")).is_stack = True; - - #---------- Layout ------------ - # layout.label(text="Layout") - - box = layout.box() - col = box.column(align=True) - - if bpy.context.active_object and bpy.context.active_object.mode == 'EDIT' and bpy.context.scene.tool_settings.use_uv_select_sync: - row = col.row(align=True) - row.alert = True - row.operator("uv.op_disable_uv_sync", text="Disable sync", icon='CANCEL')#, icon='UV_SYNC_SELECT' - - - row = col.row(align=True) - row.operator(op_uv_crop.op.bl_idname, text="Crop", icon_value = icon_get("op_uv_crop")) - row.operator(op_uv_fill.op.bl_idname, text="Fill", icon_value = icon_get("op_uv_fill")) - - - row = col.row(align=True) - row.operator(op_island_align_edge.op.bl_idname, text="Align Edge", icon_value = icon_get("op_island_align_edge")) - - row = col.row(align=True) - row.operator(op_island_align_world.op.bl_idname, text="Align World", icon_value = icon_get("op_island_align_world")) - - - if bpy.app.debug_value != 0: - c = col.column(align=True) - c.alert = True - - c.operator(op_edge_split_bevel.op.bl_idname, text="Split Bevel") - - col.separator() - - col_tr = col.column(align=True) - - row = col_tr.row(align=True) - col = row.column(align=True) - col.label(text="") - col.operator(op_align.op.bl_idname, text="←", icon_value = icon_get("op_align_left")).direction = "left" - - col = row.column(align=True) - col.operator(op_align.op.bl_idname, text="↑", icon_value = icon_get("op_align_top")).direction = "top" - col.operator(op_align.op.bl_idname, text="↓", icon_value = icon_get("op_align_bottom")).direction = "bottom" - - col = row.column(align=True) - col.label(text="") - col.operator(op_align.op.bl_idname, text="→", icon_value = icon_get("op_align_right")).direction = "right" - - row = col_tr.row(align=True) - row.operator(op_island_rotate_90.op.bl_idname, text="-90°", icon_value = icon_get("op_island_rotate_90_left")).angle = -math.pi / 2 - row.operator(op_island_rotate_90.op.bl_idname, text="+90°", icon_value = icon_get("op_island_rotate_90_right")).angle = math.pi / 2 - - - - - col = box.column(align=True) - row = col.row(align=True) - op = row.operator(op_island_align_sort.op.bl_idname, text="Sort H", icon_value = icon_get("op_island_align_sort_h")) - op.is_vertical = False; - op.padding = utilities_ui.get_padding() - - op = row.operator(op_island_align_sort.op.bl_idname, text="Sort V", icon_value = icon_get("op_island_align_sort_v")) - op.is_vertical = True; - op.padding = utilities_ui.get_padding() - - - aligned = box.row(align=True) - col = aligned.column(align=True) - - row = col.row(align=True) - row.operator(op_island_straighten_edge_loops.op.bl_idname, text="Straight", icon_value = icon_get("op_island_straighten_edge_loops")) - row.operator(op_rectify.op.bl_idname, text="Rectify", icon_value = icon_get("op_rectify")) - - - col.operator(op_unwrap_edge_peel.op.bl_idname, text="Edge Peel", icon_value = icon_get("op_unwrap_edge_peel")) - - row = col.row(align=True) - row.scale_y = 1.75 - row.operator(op_unwrap_faces_iron.op.bl_idname, text="Iron Faces", icon_value = icon_get("op_unwrap_faces_iron")) - - - col.separator() - - # col = box.column(align=True) - row = col.row(align=True) - row.label(text="" , icon_value = icon_get("texel_density")) - row.separator() - row.prop(context.scene.texToolsSettings, "texel_density", text="") - row.operator(op_texel_density_get.op.bl_idname, text="", icon = 'EYEDROPPER') - - row = col.row(align=True) - row.operator(op_texel_density_set.op.bl_idname, text="Apply", icon = 'FACESEL') - row.prop(context.scene.texToolsSettings, "texel_mode_scale", text = "", expand=False) - - #---------- Selection ------------ - - - # /box = layout.box() - # box.label(text="Select") - # col = box.column(align=True) - col.separator() - - row = col.row(align=True) - row.operator(op_select_islands_identical.op.bl_idname, text="Similar", icon_value = icon_get("op_select_islands_identical")) - row.operator(op_select_islands_overlap.op.bl_idname, text="Overlap", icon_value = icon_get("op_select_islands_overlap")) - - row = col.row(align=True) - row.operator(op_select_islands_outline.op.bl_idname, text="Bounds", icon_value = icon_get("op_select_islands_outline")) - row.operator(op_select_islands_flipped.op.bl_idname, text="Flipped", icon_value = icon_get('op_select_islands_flipped')) - - col.separator() - col.operator(op_smoothing_uv_islands.op.bl_idname, text="UV Smoothing", icon_value = icon_get("op_smoothing_uv_islands")) - + bl_label = " " + bl_space_type = 'IMAGE_EDITOR' + bl_region_type = 'UI' + bl_category = "TexTools" + bl_options = {'DEFAULT_CLOSED'} + + def draw_header(self, _): + layout = self.layout + row = layout.row(align=True) + row.operator("wm.url_open", text="", icon='INFO').url = "http://renderhjs.net/textools/blender/index.html#uvlayout" + row.label(text ="UV Layout") + + # def draw_header(self, _): + # layout = self.layout + # layout.label(text="", icon_value=icon("logo")) + + + def draw(self, context): + layout = self.layout + + + if bpy.app.debug_value != 0: + col = layout.column(align=True) + col.alert = True + row = col.row(align=True) + row.operator(op_island_mirror.op.bl_idname, text="Mirror", icon_value = icon_get("op_island_mirror")).is_stack = False; + row.operator(op_island_mirror.op.bl_idname, text="Stack", icon_value = icon_get("op_island_mirror")).is_stack = True; + + #---------- Layout ------------ + # layout.label(text="Layout") + + box = layout.box() + col = box.column(align=True) + + if bpy.context.active_object and bpy.context.active_object.mode == 'EDIT' and bpy.context.scene.tool_settings.use_uv_select_sync: + row = col.row(align=True) + row.alert = True + row.operator("uv.op_disable_uv_sync", text="Disable sync", icon='CANCEL')#, icon='UV_SYNC_SELECT' + + + row = col.row(align=True) + row.operator(op_uv_crop.op.bl_idname, text="Crop", icon_value = icon_get("op_uv_crop")) + row.operator(op_uv_fill.op.bl_idname, text="Fill", icon_value = icon_get("op_uv_fill")) + + + row = col.row(align=True) + row.operator(op_island_align_edge.op.bl_idname, text="Align Edge", icon_value = icon_get("op_island_align_edge")) + + row = col.row(align=True) + row.operator(op_island_align_world.op.bl_idname, text="Align World", icon_value = icon_get("op_island_align_world")) + + + if bpy.app.debug_value != 0: + c = col.column(align=True) + c.alert = True + + c.operator(op_edge_split_bevel.op.bl_idname, text="Split Bevel") + + col.separator() + + col_tr = col.column(align=True) + + row = col_tr.row(align=True) + col = row.column(align=True) + col.label(text="") + col.operator(op_align.op.bl_idname, text="←", icon_value = icon_get("op_align_left")).direction = "left" + + col = row.column(align=True) + col.operator(op_align.op.bl_idname, text="↑", icon_value = icon_get("op_align_top")).direction = "top" + col.operator(op_align.op.bl_idname, text="↓", icon_value = icon_get("op_align_bottom")).direction = "bottom" + + col = row.column(align=True) + col.label(text="") + col.operator(op_align.op.bl_idname, text="→", icon_value = icon_get("op_align_right")).direction = "right" + + row = col_tr.row(align=True) + row.operator(op_island_rotate_90.op.bl_idname, text="-90°", icon_value = icon_get("op_island_rotate_90_left")).angle = -math.pi / 2 + row.operator(op_island_rotate_90.op.bl_idname, text="+90°", icon_value = icon_get("op_island_rotate_90_right")).angle = math.pi / 2 + + + + + col = box.column(align=True) + row = col.row(align=True) + op = row.operator(op_island_align_sort.op.bl_idname, text="Sort H", icon_value = icon_get("op_island_align_sort_h")) + op.is_vertical = False; + op.padding = utilities_ui.get_padding() + + op = row.operator(op_island_align_sort.op.bl_idname, text="Sort V", icon_value = icon_get("op_island_align_sort_v")) + op.is_vertical = True; + op.padding = utilities_ui.get_padding() + + + aligned = box.row(align=True) + col = aligned.column(align=True) + + row = col.row(align=True) + row.operator(op_island_straighten_edge_loops.op.bl_idname, text="Straight", icon_value = icon_get("op_island_straighten_edge_loops")) + row.operator(op_rectify.op.bl_idname, text="Rectify", icon_value = icon_get("op_rectify")) + + + col.operator(op_unwrap_edge_peel.op.bl_idname, text="Edge Peel", icon_value = icon_get("op_unwrap_edge_peel")) + + row = col.row(align=True) + row.scale_y = 1.75 + row.operator(op_unwrap_faces_iron.op.bl_idname, text="Iron Faces", icon_value = icon_get("op_unwrap_faces_iron")) + + + col.separator() + + # col = box.column(align=True) + row = col.row(align=True) + row.label(text="" , icon_value = icon_get("texel_density")) + row.separator() + row.prop(context.scene.texToolsSettings, "texel_density", text="") + row.operator(op_texel_density_get.op.bl_idname, text="", icon = 'EYEDROPPER') + + row = col.row(align=True) + row.operator(op_texel_density_set.op.bl_idname, text="Apply", icon = 'FACESEL') + row.prop(context.scene.texToolsSettings, "texel_mode_scale", text = "", expand=False) + + #---------- Selection ------------ + + + # /box = layout.box() + # box.label(text="Select") + # col = box.column(align=True) + col.separator() + + row = col.row(align=True) + row.operator(op_select_islands_identical.op.bl_idname, text="Similar", icon_value = icon_get("op_select_islands_identical")) + row.operator(op_select_islands_overlap.op.bl_idname, text="Overlap", icon_value = icon_get("op_select_islands_overlap")) + + row = col.row(align=True) + row.operator(op_select_islands_outline.op.bl_idname, text="Bounds", icon_value = icon_get("op_select_islands_outline")) + row.operator(op_select_islands_flipped.op.bl_idname, text="Flipped", icon_value = icon_get('op_select_islands_flipped')) + + col.separator() + col.operator(op_smoothing_uv_islands.op.bl_idname, text="UV Smoothing", icon_value = icon_get("op_smoothing_uv_islands")) + class UI_PT_Panel_Bake(bpy.types.Panel): - bl_label = " " - bl_space_type = 'IMAGE_EDITOR' - bl_region_type = 'UI' - bl_category = "TexTools" - bl_options = {'DEFAULT_CLOSED'} - - def draw_header(self, _): - layout = self.layout - row = layout.row(align=True) - row.operator("wm.url_open", text="", icon='INFO').url = "http://renderhjs.net/textools/blender/index.html#texturebaking" - row.label(text ="Baking") - - def draw(self, context): - layout = self.layout - - #----------- Baking ------------- - row = layout.row() - box = row.box() - col = box.column(align=True) - - if not (bpy.context.scene.texToolsSettings.bake_freeze_selection and len(settings.sets) > 0): - # Update sets - settings.sets = utilities_bake.get_bake_sets() - - - # Bake Button - count = 0 - if bpy.context.scene.texToolsSettings.bake_force_single and len(settings.sets) > 0: - count = 1 - else: - count = len(settings.sets) - - row = col.row(align=True) - row.scale_y = 1.75 - row.operator(op_bake.op.bl_idname, text = "Bake {}x".format(count), icon_value = icon_get("op_bake")); - - # anti aliasing - col.prop(context.scene.texToolsSettings, "bake_sampling", icon_value =icon_get("bake_anti_alias")) - - if bpy.app.debug_value != 0: - row = col.row() - row.alert = True - row.prop(context.scene.texToolsSettings, "bake_force_single", text="Dither Floats") - - col.separator() - - - # Collected Related Textures - col.separator() - - row = col.row(align=True) - row.scale_y = 1.5 - row.operator(op_texture_preview.op.bl_idname, text = "Preview Texture", icon_value = icon_get("op_texture_preview")); - - images = utilities_bake.get_baked_images(settings.sets) - - if len(images) > 0: - - image_background = None - for area in bpy.context.screen.areas: - if area.type == 'IMAGE_EDITOR': - if area.spaces[0].image: - image_background = area.spaces[0].image - break - - box = col.box() - # box.label(text="{}x images".format(len(images)), icon="IMAGE_DATA") - col_box = box.column(align=True) - for image in images: - row = col_box.row(align=True) - - # row.label(text=image.name, icon='') - icon = 'RADIOBUT_OFF' - if image == image_background: - icon = 'RADIOBUT_ON' - row.operator(op_texture_select.op.bl_idname, text=image.name, icon=icon).name = image.name #, - - row = row.row(align=True) - row.alignment = 'RIGHT' - if image.filepath != "": - row.operator(op_texture_open.op.bl_idname, text="", icon_value=icon_get("op_texture_open") ).name = image.name - else: - if bpy.app.debug_value != 0: - row.operator(op_texture_save.op.bl_idname, text="", icon_value=icon_get("op_texture_save") ).name = image.name - else: - pass - - row.operator(op_texture_remove.op.bl_idname, text="", icon='X' ).name = image.name - - - col.separator() - - - - # Bake Mode - col.template_icon_view(bpy.context.scene, "TT_bake_mode") - - - if bpy.app.debug_value != 0: - row = col.row() - row.label(text="--> Mode: '{}'".format(bpy.context.scene.TT_bake_mode)) - - bake_mode = utilities_ui.get_bake_mode() - - # Warning: Wrong bake mode, require - if bake_mode == 'diffuse': - if bpy.context.scene.render.engine != 'CYCLES': - if bpy.context.scene.render.engine != op_bake.modes[bake_mode].engine: - col.label(text="Requires '{}'".format(op_bake.modes[bake_mode].engine), icon='ERROR') - - - - - # Optional Parameters - col.separator() - for set in settings.sets: - if len(set.objects_low) > 0 and len(set.objects_high) > 0: - col.prop(context.scene.texToolsSettings, "bake_ray_distance") - break - - # Display Bake mode properties / parameters - if bake_mode in op_bake.modes: - params = op_bake.modes[bake_mode].params - if len(params) > 0: - for param in params: - col.prop(context.scene.texToolsSettings, param) - - # Warning about projection requirement - if len(settings.sets) > 0 and op_bake.modes[bake_mode].use_project == True: - if len(settings.sets[0].objects_low) == 0 or len(settings.sets[0].objects_high) == 0: - col.label(text="Need high and low", icon='ERROR') - - - - box = layout.box() - col = box.column(align=True) - - # Select by type - if len(settings.sets) > 0: - row = col.row(align=True) - row.active = len(settings.sets) > 0 - - count_types = { - 'low':0, 'high':0, 'cage':0, 'float':0, 'issue':0, - } - for set in settings.sets: - if set.has_issues: - count_types['issue']+=1 - if len(set.objects_low) > 0: - count_types['low']+=1 - if len(set.objects_high) > 0: - count_types['high']+=1 - if len(set.objects_cage) > 0: - count_types['cage']+=1 - if len(set.objects_float) > 0: - count_types['float']+=1 - - # Freeze Selection - c = row.column(align=True) - c.active = len(settings.sets) > 0 or bpy.context.scene.texToolsSettings.bake_freeze_selection - icon = 'LOCKED' if bpy.context.scene.texToolsSettings.bake_freeze_selection else 'UNLOCKED' - c.prop(context.scene.texToolsSettings, "bake_freeze_selection",text="Lock {}x".format(len(settings.sets)), icon=icon) - - # Select by type - if count_types['issue'] > 0: - row.operator("uv.op_select_bake_type", text = "", icon = 'ERROR').select_type = 'issue' - - row.operator("uv.op_select_bake_type", text = "", icon_value = icon_get("bake_obj_low")).select_type = 'low' - row.operator("uv.op_select_bake_type", text = "", icon_value = icon_get("bake_obj_high")).select_type = 'high' - - if count_types['float'] > 0: - row.operator("uv.op_select_bake_type", text = "", icon_value = icon_get("bake_obj_float")).select_type = 'float' - - if count_types['cage'] > 0: - row.operator("uv.op_select_bake_type", text = "", icon_value = icon_get("bake_obj_cage")).select_type = 'cage' - - # List bake sets - box2 = box.box() - row = box2.row() - split = None - - countTypes = (0 if count_types['low'] == 0 else 1) + (0 if count_types['high'] == 0 else 1) + (0 if count_types['cage'] == 0 else 1) + (0 if count_types['float'] == 0 else 1) - if countTypes > 2: - # More than 3 types, use less space for label - split = row.split(factor=0.45) - else: - # Only 2 or less types, use more space for label - split = row.split(factor=0.55) - - c = split.column(align=True) - for s in range(0, len(settings.sets)): - set = settings.sets[s] - r = c.row(align=True) - r.active = not (bpy.context.scene.texToolsSettings.bake_force_single and s > 0) - - if set.has_issues: - r.operator("uv.op_select_bake_set", text=set.name, icon='ERROR').select_set = set.name - else: - r.operator("uv.op_select_bake_set", text=set.name).select_set = set.name - - - c = split.column(align=True) - for set in settings.sets: - r = c.row(align=True) - r.alignment = "LEFT" - - if len(set.objects_low) > 0: - r.label(text="{}".format(len(set.objects_low)), icon_value = icon_get("bake_obj_low")) - elif count_types['low'] > 0: - r.label(text="") - - if len(set.objects_high) > 0: - r.label(text="{}".format(len(set.objects_high)), icon_value = icon_get("bake_obj_high")) - elif count_types['high'] > 0: - r.label(text="") - - if len(set.objects_float) > 0: - r.label(text="{}".format(len(set.objects_float)), icon_value = icon_get("bake_obj_float")) - elif count_types['float'] > 0: - r.label(text="") - - if len(set.objects_cage) > 0: - r.label(text="{}".format(len(set.objects_cage)), icon_value = icon_get("bake_obj_cage")) - elif count_types['cage'] > 0: - r.label(text="") - - # Force Single - row = box2.row(align=True) - row.active = len(settings.sets) > 0 - row.prop(context.scene.texToolsSettings, "bake_force_single", text="Single Texture") - if len(settings.sets) > 0 and bpy.context.scene.texToolsSettings.bake_force_single: - row.label(text="'{}'".format(settings.sets[0].name)) - # else: - # row.label(text="") - - - - - col = box.column(align=True) - col.operator(op_bake_organize_names.op.bl_idname, text = "Organize {}x".format(len(bpy.context.selected_objects)), icon = 'BOOKMARKS') - col.operator(op_bake_explode.op.bl_idname, text = "Explode", icon_value = icon_get("op_bake_explode")); - - - - - + bl_label = " " + bl_space_type = 'IMAGE_EDITOR' + bl_region_type = 'UI' + bl_category = "TexTools" + bl_options = {'DEFAULT_CLOSED'} + + def draw_header(self, _): + layout = self.layout + row = layout.row(align=True) + row.operator("wm.url_open", text="", icon='INFO').url = "http://renderhjs.net/textools/blender/index.html#texturebaking" + row.label(text ="Baking") + + def draw(self, context): + layout = self.layout + + #----------- Baking ------------- + row = layout.row() + box = row.box() + col = box.column(align=True) + + if not (bpy.context.scene.texToolsSettings.bake_freeze_selection and len(settings.sets) > 0): + # Update sets + settings.sets = utilities_bake.get_bake_sets() + + + # Bake Button + count = 0 + if bpy.context.scene.texToolsSettings.bake_force_single and len(settings.sets) > 0: + count = 1 + else: + count = len(settings.sets) + + row = col.row(align=True) + row.scale_y = 1.75 + row.operator(op_bake.op.bl_idname, text = "Bake {}x".format(count), icon_value = icon_get("op_bake")); + + # anti aliasing + col.prop(context.scene.texToolsSettings, "bake_sampling", icon_value =icon_get("bake_anti_alias")) + + if bpy.app.debug_value != 0: + row = col.row() + row.alert = True + row.prop(context.scene.texToolsSettings, "bake_force_single", text="Dither Floats") + + col.separator() + + + # Collected Related Textures + col.separator() + + row = col.row(align=True) + row.scale_y = 1.5 + row.operator(op_texture_preview.op.bl_idname, text = "Preview Texture", icon_value = icon_get("op_texture_preview")); + + images = utilities_bake.get_baked_images(settings.sets) + + if len(images) > 0: + + image_background = None + for area in bpy.context.screen.areas: + if area.type == 'IMAGE_EDITOR': + if area.spaces[0].image: + image_background = area.spaces[0].image + break + + box = col.box() + # box.label(text="{}x images".format(len(images)), icon="IMAGE_DATA") + col_box = box.column(align=True) + for image in images: + row = col_box.row(align=True) + + # row.label(text=image.name, icon='') + icon = 'RADIOBUT_OFF' + if image == image_background: + icon = 'RADIOBUT_ON' + row.operator(op_texture_select.op.bl_idname, text=image.name, icon=icon).name = image.name #, + + row = row.row(align=True) + row.alignment = 'RIGHT' + if image.filepath != "": + row.operator(op_texture_open.op.bl_idname, text="", icon_value=icon_get("op_texture_open") ).name = image.name + else: + if bpy.app.debug_value != 0: + row.operator(op_texture_save.op.bl_idname, text="", icon_value=icon_get("op_texture_save") ).name = image.name + else: + pass + + row.operator(op_texture_remove.op.bl_idname, text="", icon='X' ).name = image.name + + + col.separator() + + + + # Bake Mode + col.template_icon_view(bpy.context.scene, "TT_bake_mode") + + + if bpy.app.debug_value != 0: + row = col.row() + row.label(text="--> Mode: '{}'".format(bpy.context.scene.TT_bake_mode)) + + bake_mode = utilities_ui.get_bake_mode() + + # Warning: Wrong bake mode, require + if bake_mode == 'diffuse': + if bpy.context.scene.render.engine != 'CYCLES': + if bpy.context.scene.render.engine != op_bake.modes[bake_mode].engine: + col.label(text="Requires '{}'".format(op_bake.modes[bake_mode].engine), icon='ERROR') + + + + + # Optional Parameters + col.separator() + for set in settings.sets: + if len(set.objects_low) > 0 and len(set.objects_high) > 0: + col.prop(context.scene.texToolsSettings, "bake_ray_distance") + break + + # Display Bake mode properties / parameters + if bake_mode in op_bake.modes: + params = op_bake.modes[bake_mode].params + if len(params) > 0: + for param in params: + col.prop(context.scene.texToolsSettings, param) + + # Warning about projection requirement + if len(settings.sets) > 0 and op_bake.modes[bake_mode].use_project == True: + if len(settings.sets[0].objects_low) == 0 or len(settings.sets[0].objects_high) == 0: + col.label(text="Need high and low", icon='ERROR') + + + + box = layout.box() + col = box.column(align=True) + + # Select by type + if len(settings.sets) > 0: + row = col.row(align=True) + row.active = len(settings.sets) > 0 + + count_types = { + 'low':0, 'high':0, 'cage':0, 'float':0, 'issue':0, + } + for set in settings.sets: + if set.has_issues: + count_types['issue']+=1 + if len(set.objects_low) > 0: + count_types['low']+=1 + if len(set.objects_high) > 0: + count_types['high']+=1 + if len(set.objects_cage) > 0: + count_types['cage']+=1 + if len(set.objects_float) > 0: + count_types['float']+=1 + + # Freeze Selection + c = row.column(align=True) + c.active = len(settings.sets) > 0 or bpy.context.scene.texToolsSettings.bake_freeze_selection + icon = 'LOCKED' if bpy.context.scene.texToolsSettings.bake_freeze_selection else 'UNLOCKED' + c.prop(context.scene.texToolsSettings, "bake_freeze_selection",text="Lock {}x".format(len(settings.sets)), icon=icon) + + # Select by type + if count_types['issue'] > 0: + row.operator("uv.op_select_bake_type", text = "", icon = 'ERROR').select_type = 'issue' + + row.operator("uv.op_select_bake_type", text = "", icon_value = icon_get("bake_obj_low")).select_type = 'low' + row.operator("uv.op_select_bake_type", text = "", icon_value = icon_get("bake_obj_high")).select_type = 'high' + + if count_types['float'] > 0: + row.operator("uv.op_select_bake_type", text = "", icon_value = icon_get("bake_obj_float")).select_type = 'float' + + if count_types['cage'] > 0: + row.operator("uv.op_select_bake_type", text = "", icon_value = icon_get("bake_obj_cage")).select_type = 'cage' + + # List bake sets + box2 = box.box() + row = box2.row() + split = None + + countTypes = (0 if count_types['low'] == 0 else 1) + (0 if count_types['high'] == 0 else 1) + (0 if count_types['cage'] == 0 else 1) + (0 if count_types['float'] == 0 else 1) + if countTypes > 2: + # More than 3 types, use less space for label + split = row.split(factor=0.45) + else: + # Only 2 or less types, use more space for label + split = row.split(factor=0.55) + + c = split.column(align=True) + for s in range(0, len(settings.sets)): + set = settings.sets[s] + r = c.row(align=True) + r.active = not (bpy.context.scene.texToolsSettings.bake_force_single and s > 0) + + if set.has_issues: + r.operator("uv.op_select_bake_set", text=set.name, icon='ERROR').select_set = set.name + else: + r.operator("uv.op_select_bake_set", text=set.name).select_set = set.name + + + c = split.column(align=True) + for set in settings.sets: + r = c.row(align=True) + r.alignment = "LEFT" + + if len(set.objects_low) > 0: + r.label(text="{}".format(len(set.objects_low)), icon_value = icon_get("bake_obj_low")) + elif count_types['low'] > 0: + r.label(text="") + + if len(set.objects_high) > 0: + r.label(text="{}".format(len(set.objects_high)), icon_value = icon_get("bake_obj_high")) + elif count_types['high'] > 0: + r.label(text="") + + if len(set.objects_float) > 0: + r.label(text="{}".format(len(set.objects_float)), icon_value = icon_get("bake_obj_float")) + elif count_types['float'] > 0: + r.label(text="") + + if len(set.objects_cage) > 0: + r.label(text="{}".format(len(set.objects_cage)), icon_value = icon_get("bake_obj_cage")) + elif count_types['cage'] > 0: + r.label(text="") + + # Force Single + row = box2.row(align=True) + row.active = len(settings.sets) > 0 + row.prop(context.scene.texToolsSettings, "bake_force_single", text="Single Texture") + if len(settings.sets) > 0 and bpy.context.scene.texToolsSettings.bake_force_single: + row.label(text="'{}'".format(settings.sets[0].name)) + # else: + # row.label(text="") + + + + + col = box.column(align=True) + col.operator(op_bake_organize_names.op.bl_idname, text = "Organize {}x".format(len(bpy.context.selected_objects)), icon = 'BOOKMARKS') + col.operator(op_bake_explode.op.bl_idname, text = "Explode", icon_value = icon_get("op_bake_explode")); + + + + + class UI_MT_op_color_dropdown_io(bpy.types.Menu): - bl_idname = "UI_MT_op_color_dropdown_io" - bl_label = "IO" + bl_idname = "UI_MT_op_color_dropdown_io" + bl_label = "IO" - def draw(self, context): - layout = self.layout + def draw(self, context): + layout = self.layout - layout.operator(op_color_io_export.op.bl_idname, text="Export Colors", icon = 'EXPORT') - layout.operator(op_color_io_import.op.bl_idname, text="Import Colors", icon = 'IMPORT') + layout.operator(op_color_io_export.op.bl_idname, text="Export Colors", icon = 'EXPORT') + layout.operator(op_color_io_import.op.bl_idname, text="Import Colors", icon = 'IMPORT') class UI_MT_op_color_dropdown_convert_from(bpy.types.Menu): - bl_idname = "UI_MT_op_color_dropdown_convert_from" - bl_label = "From" - bl_description = "Create Color IDs from ..." + bl_idname = "UI_MT_op_color_dropdown_convert_from" + bl_label = "From" + bl_description = "Create Color IDs from ..." - def draw(self, context): - layout = self.layout - layout.operator(op_color_from_elements.op.bl_idname, text="Mesh Elements", icon_value = icon_get('op_color_from_elements')) - layout.operator(op_color_from_materials.op.bl_idname, text="Materials", icon_value = icon_get('op_color_from_materials')) - layout.operator(op_color_from_directions.op.bl_idname, text="Directions", icon_value = icon_get('op_color_from_directions')) - + def draw(self, context): + layout = self.layout + layout.operator(op_color_from_elements.op.bl_idname, text="Mesh Elements", icon_value = icon_get('op_color_from_elements')) + layout.operator(op_color_from_materials.op.bl_idname, text="Materials", icon_value = icon_get('op_color_from_materials')) + layout.operator(op_color_from_directions.op.bl_idname, text="Directions", icon_value = icon_get('op_color_from_directions')) + class UI_MT_op_color_dropdown_convert_to(bpy.types.Menu): - bl_idname = "UI_MT_op_color_dropdown_convert_to" - bl_label = "To" - bl_description = "Convert Color IDs into ..." + bl_idname = "UI_MT_op_color_dropdown_convert_to" + bl_label = "To" + bl_description = "Convert Color IDs into ..." - def draw(self, context): - layout = self.layout - layout.operator(op_color_convert_texture.op.bl_idname, text="Texture Atlas", icon_value = icon_get('op_color_convert_texture')) - layout.operator(op_color_convert_vertex_colors.op.bl_idname, text="Vertex Colors", icon_value = icon_get("op_color_convert_vertex_colors")) + def draw(self, context): + layout = self.layout + layout.operator(op_color_convert_texture.op.bl_idname, text="Texture Atlas", icon_value = icon_get('op_color_convert_texture')) + layout.operator(op_color_convert_vertex_colors.op.bl_idname, text="Vertex Colors", icon_value = icon_get("op_color_convert_vertex_colors")) class UV_OT_op_enable_cycles(bpy.types.Operator): - bl_idname = "uv.textools_enable_cycles" - bl_label = "Enable Cycles" - bl_description = "Enable Cycles render engine" + bl_idname = "uv.textools_enable_cycles" + bl_label = "Enable Cycles" + bl_description = "Enable Cycles render engine" - @classmethod - def poll(cls, context): - return True + @classmethod + def poll(cls, context): + return True - def execute(self, context): - bpy.context.scene.render.engine = 'CYCLES' - return {'FINISHED'} + def execute(self, context): + bpy.context.scene.render.engine = 'CYCLES' + return {'FINISHED'} class UI_PT_Panel_Colors(bpy.types.Panel): - bl_label = " " - bl_space_type = 'IMAGE_EDITOR' - bl_region_type = 'UI' - bl_category = "TexTools" - bl_options = {'DEFAULT_CLOSED'} - - def draw_header(self, _): - layout = self.layout - row = layout.row(align=True) - row.operator("wm.url_open", text="", icon='INFO').url = "http://renderhjs.net/textools/blender/index.html#colorid" - row.label(text ="Color ID") - - def draw(self, context): - layout = self.layout - - # layout.label(text="Select face and color") - - if bpy.context.scene.render.engine != 'CYCLES' and bpy.context.scene.render.engine != 'BLENDER_EEVEE': - row = layout.row(align=True) - row.alert = True - row.operator("uv.op_enable_cycles", text="Enable 'CYCLES'", icon='CANCEL')#, icon='UV_SYNC_SELECT' - return - - - box = layout.box() - col = box.column(align=True) - - - - row = col.row(align=True) - split = row.split(factor=0.60, align=True) - c = split.column(align=True) - c.prop(context.scene.texToolsSettings, "color_ID_templates", text="") - c = split.column(align=True) - c.prop(context.scene.texToolsSettings, "color_ID_count", text="", expand=False) - - row = box.row(align=True) - row.operator(op_color_clear.op.bl_idname, text="Clear", icon = 'X') - row.menu(UI_MT_op_color_dropdown_io.bl_idname, icon='COLOR') - - - max_columns = 5 - if context.scene.texToolsSettings.color_ID_count < max_columns: - max_columns = context.scene.texToolsSettings.color_ID_count - - count = math.ceil(context.scene.texToolsSettings.color_ID_count / max_columns)*max_columns - - for i in range(count): - - if i%max_columns == 0: - row = box.row(align=True) - - col = row.column(align=True) - if i < context.scene.texToolsSettings.color_ID_count: - col.prop(context.scene.texToolsSettings, "color_ID_color_{}".format(i), text="") - col.operator(op_color_assign.op.bl_idname, text="", icon = "FILE_TICK").index = i - - if bpy.context.active_object: - if bpy.context.active_object in bpy.context.selected_objects: - if len(bpy.context.selected_objects) == 1: - if bpy.context.active_object.type == 'MESH': - col.operator(op_color_select.op.bl_idname, text="", icon = "FACESEL").index = i - else: - col.label(text=" ") - - - # split = row.split(percentage=0.25, align=True) - # c = split.column(align=True) - # c.operator(op_color_clear.op.bl_idname, text="", icon = 'X') - # c = split.column(align=True) - # c.operator(op_color_from_elements.op.bl_idname, text="Color Elements", icon_value = icon_get('op_color_from_elements')) - - - - col = box.column(align=True) - col.label(text="Convert") - row = col.row(align=True) - row.menu(UI_MT_op_color_dropdown_convert_from.bl_idname)#, icon='IMPORT' - row.menu(UI_MT_op_color_dropdown_convert_to.bl_idname,)# icon='EXPORT' - - - - - # row = col.row(align=True) - # row.operator(op_color_convert_texture.op.bl_idname, text="From Atlas", icon_value = icon_get('op_color_convert_texture')) - - - - # for i in range(context.scene.texToolsSettings.color_ID_count): - - - - # col = row.column(align=True) - # col.prop(context.scene.texToolsSettings, "color_ID_color_{}".format(i), text="") - # col.operator(op_color_assign.op.bl_idname, text="", icon = "FILE_TICK").index = i - - # if bpy.context.active_object: - # if bpy.context.active_object.type == 'MESH': - # if bpy.context.active_object.mode == 'EDIT': - # col.operator(op_color_select.op.bl_idname, text="", icon = "FACESEL").index = i - - + bl_label = " " + bl_space_type = 'IMAGE_EDITOR' + bl_region_type = 'UI' + bl_category = "TexTools" + bl_options = {'DEFAULT_CLOSED'} + + def draw_header(self, _): + layout = self.layout + row = layout.row(align=True) + row.operator("wm.url_open", text="", icon='INFO').url = "http://renderhjs.net/textools/blender/index.html#colorid" + row.label(text ="Color ID") + + def draw(self, context): + layout = self.layout + + # layout.label(text="Select face and color") + + if bpy.context.scene.render.engine != 'CYCLES' and bpy.context.scene.render.engine != 'BLENDER_EEVEE': + row = layout.row(align=True) + row.alert = True + row.operator("uv.op_enable_cycles", text="Enable 'CYCLES'", icon='CANCEL')#, icon='UV_SYNC_SELECT' + return + + + box = layout.box() + col = box.column(align=True) + + + + row = col.row(align=True) + split = row.split(factor=0.60, align=True) + c = split.column(align=True) + c.prop(context.scene.texToolsSettings, "color_ID_templates", text="") + c = split.column(align=True) + c.prop(context.scene.texToolsSettings, "color_ID_count", text="", expand=False) + + row = box.row(align=True) + row.operator(op_color_clear.op.bl_idname, text="Clear", icon = 'X') + row.menu(UI_MT_op_color_dropdown_io.bl_idname, icon='COLOR') + + + max_columns = 5 + if context.scene.texToolsSettings.color_ID_count < max_columns: + max_columns = context.scene.texToolsSettings.color_ID_count + + count = math.ceil(context.scene.texToolsSettings.color_ID_count / max_columns)*max_columns + + for i in range(count): + + if i%max_columns == 0: + row = box.row(align=True) + + col = row.column(align=True) + if i < context.scene.texToolsSettings.color_ID_count: + col.prop(context.scene.texToolsSettings, "color_ID_color_{}".format(i), text="") + col.operator(op_color_assign.op.bl_idname, text="", icon = "FILE_TICK").index = i + + if bpy.context.active_object: + if bpy.context.active_object in bpy.context.selected_objects: + if len(bpy.context.selected_objects) == 1: + if bpy.context.active_object.type == 'MESH': + col.operator(op_color_select.op.bl_idname, text="", icon = "FACESEL").index = i + else: + col.label(text=" ") + + + # split = row.split(percentage=0.25, align=True) + # c = split.column(align=True) + # c.operator(op_color_clear.op.bl_idname, text="", icon = 'X') + # c = split.column(align=True) + # c.operator(op_color_from_elements.op.bl_idname, text="Color Elements", icon_value = icon_get('op_color_from_elements')) + + + + col = box.column(align=True) + col.label(text="Convert") + row = col.row(align=True) + row.menu(UI_MT_op_color_dropdown_convert_from.bl_idname)#, icon='IMPORT' + row.menu(UI_MT_op_color_dropdown_convert_to.bl_idname,)# icon='EXPORT' + + + + + # row = col.row(align=True) + # row.operator(op_color_convert_texture.op.bl_idname, text="From Atlas", icon_value = icon_get('op_color_convert_texture')) + + + + # for i in range(context.scene.texToolsSettings.color_ID_count): + + + + # col = row.column(align=True) + # col.prop(context.scene.texToolsSettings, "color_ID_color_{}".format(i), text="") + # col.operator(op_color_assign.op.bl_idname, text="", icon = "FILE_TICK").index = i + + # if bpy.context.active_object: + # if bpy.context.active_object.type == 'MESH': + # if bpy.context.active_object.mode == 'EDIT': + # col.operator(op_color_select.op.bl_idname, text="", icon = "FACESEL").index = i + + - # https://github.com/blenderskool/kaleidoscope/blob/fb5cb1ab87a57b46618d99afaf4d3154ad934529/spectrum.py - - + # https://github.com/blenderskool/kaleidoscope/blob/fb5cb1ab87a57b46618d99afaf4d3154ad934529/spectrum.py + + - + class UI_PT_Panel_MeshTexture(bpy.types.Panel): - bl_label = " " - bl_space_type = 'IMAGE_EDITOR' - bl_region_type = 'UI' - bl_category = "TexTools" - bl_options = {'DEFAULT_CLOSED'} + bl_label = " " + bl_space_type = 'IMAGE_EDITOR' + bl_region_type = 'UI' + bl_category = "TexTools" + bl_options = {'DEFAULT_CLOSED'} - def draw_header(self, _): - layout = self.layout - row = layout.row(align=True) - row.operator("wm.url_open", text="", icon='INFO').url = "http://renderhjs.net/textools/blender/index.html#meshtexture" - row.label(text ="Mesh Texture") + def draw_header(self, _): + layout = self.layout + row = layout.row(align=True) + row.operator("wm.url_open", text="", icon='INFO').url = "http://renderhjs.net/textools/blender/index.html#meshtexture" + row.label(text ="Mesh Texture") - def draw(self, context): - layout = self.layout - box = layout.box() - col = box.column(align=True) + def draw(self, context): + layout = self.layout + box = layout.box() + col = box.column(align=True) - row = col.row(align=True) - row.scale_y = 1.5 - row.operator(op_meshtex_create.op.bl_idname, text="Create UV Mesh", icon_value = icon_get("op_meshtex_create")) - - row = col.row(align=True) - row.operator(op_meshtex_trim.op.bl_idname, text="Trim", icon_value = icon_get("op_meshtex_trim")) + row = col.row(align=True) + row.scale_y = 1.5 + row.operator(op_meshtex_create.op.bl_idname, text="Create UV Mesh", icon_value = icon_get("op_meshtex_create")) + + row = col.row(align=True) + row.operator(op_meshtex_trim.op.bl_idname, text="Trim", icon_value = icon_get("op_meshtex_trim")) - # Warning about trimmed mesh - if op_meshtex_trim_collapse.is_available(): - row.operator(op_meshtex_trim_collapse.op.bl_idname, text="Collapse Trim", icon_value=icon_get("op_meshtex_trim_collapse")) + # Warning about trimmed mesh + if op_meshtex_trim_collapse.is_available(): + row.operator(op_meshtex_trim_collapse.op.bl_idname, text="Collapse Trim", icon_value=icon_get("op_meshtex_trim_collapse")) - col = box.column(align=True) - row = col.row(align = True) - row.operator(op_meshtex_wrap.op.bl_idname, text="Wrap", icon_value = icon_get("op_meshtex_wrap")) + col = box.column(align=True) + row = col.row(align = True) + row.operator(op_meshtex_wrap.op.bl_idname, text="Wrap", icon_value = icon_get("op_meshtex_wrap")) - row = col.row(align = True) - if not utilities_meshtex.find_uv_mesh(bpy.context.selected_objects): - row.enabled = False - row.prop(context.scene.texToolsSettings, "meshtexture_wrap", text="Wrap") + row = col.row(align = True) + if not utilities_meshtex.find_uv_mesh(bpy.context.selected_objects): + row.enabled = False + row.prop(context.scene.texToolsSettings, "meshtexture_wrap", text="Wrap") - box.operator(op_meshtex_pattern.op.bl_idname, text="Create Pattern", icon_value = icon_get("op_meshtex_pattern")) + box.operator(op_meshtex_pattern.op.bl_idname, text="Create Pattern", icon_value = icon_get("op_meshtex_pattern")) @@ -1275,203 +1275,203 @@ class UI_PT_Panel_MeshTexture(bpy.types.Panel): keymaps = [] def icon_get(name): - return utilities_ui.icon_get(name) + return utilities_ui.icon_get(name) - + def menu_IMAGE_uvs(self, context): - layout = self.layout - layout.separator() - layout.operator(op_uv_resize.op.bl_idname, text="Resize", icon_value = icon_get("op_extend_canvas_open")) - layout.operator(op_rectify.op.bl_idname, text="Rectify", icon_value = icon_get("op_rectify")) - layout.operator(op_uv_crop.op.bl_idname, text="Crop", icon_value = icon_get("op_uv_crop")) - layout.operator(op_uv_fill.op.bl_idname, text="Crop", icon_value = icon_get("op_uv_fill")) - - layout.separator() - layout.operator(op_island_align_sort.op.bl_idname, text="Sort H", icon_value = icon_get("op_island_align_sort_h")) - layout.operator(op_island_align_sort.op.bl_idname, text="Sort V", icon_value = icon_get("op_island_align_sort_v")) - - layout.separator() - layout.operator(op_island_align_edge.op.bl_idname, text="Align Edge", icon_value = icon_get("op_island_align_edge")) - layout.operator(op_island_align_world.op.bl_idname, text="Align World", icon_value = icon_get("op_island_align_world")) - - layout.menu(VIEW3D_MT_submenu_align) + layout = self.layout + layout.separator() + layout.operator(op_uv_resize.op.bl_idname, text="Resize", icon_value = icon_get("op_extend_canvas_open")) + layout.operator(op_rectify.op.bl_idname, text="Rectify", icon_value = icon_get("op_rectify")) + layout.operator(op_uv_crop.op.bl_idname, text="Crop", icon_value = icon_get("op_uv_crop")) + layout.operator(op_uv_fill.op.bl_idname, text="Crop", icon_value = icon_get("op_uv_fill")) + + layout.separator() + layout.operator(op_island_align_sort.op.bl_idname, text="Sort H", icon_value = icon_get("op_island_align_sort_h")) + layout.operator(op_island_align_sort.op.bl_idname, text="Sort V", icon_value = icon_get("op_island_align_sort_v")) + + layout.separator() + layout.operator(op_island_align_edge.op.bl_idname, text="Align Edge", icon_value = icon_get("op_island_align_edge")) + layout.operator(op_island_align_world.op.bl_idname, text="Align World", icon_value = icon_get("op_island_align_world")) + + layout.menu(VIEW3D_MT_submenu_align) class VIEW3D_MT_submenu_align(bpy.types.Menu): - bl_label="Align" - bl_idname="VIEW3D_MT_submenu_align" - def draw(self, context): - layout = self.layout - layout.operator(op_align.op.bl_idname, text="←", icon_value = icon_get("op_align_left")).direction = "left" - layout.operator(op_align.op.bl_idname, text="↑", icon_value = icon_get("op_align_top")).direction = "top" - layout.operator(op_align.op.bl_idname, text="↓", icon_value = icon_get("op_align_bottom")).direction = "bottom" - layout.operator(op_align.op.bl_idname, text="→", icon_value = icon_get("op_align_right")).direction = "right" + bl_label="Align" + bl_idname="VIEW3D_MT_submenu_align" + def draw(self, context): + layout = self.layout + layout.operator(op_align.op.bl_idname, text="←", icon_value = icon_get("op_align_left")).direction = "left" + layout.operator(op_align.op.bl_idname, text="↑", icon_value = icon_get("op_align_top")).direction = "top" + layout.operator(op_align.op.bl_idname, text="↓", icon_value = icon_get("op_align_bottom")).direction = "bottom" + layout.operator(op_align.op.bl_idname, text="→", icon_value = icon_get("op_align_right")).direction = "right" def menu_IMAGE_select(self, context): - layout = self.layout - layout.separator() - layout.operator(op_select_islands_identical.op.bl_idname, text="Similar", icon_value = icon_get("op_select_islands_identical")) - layout.operator(op_select_islands_overlap.op.bl_idname, text="Overlap", icon_value = icon_get("op_select_islands_overlap")) - layout.operator(op_select_islands_outline.op.bl_idname, text="Bounds", icon_value = icon_get("op_select_islands_outline")) - layout.operator(op_select_islands_flipped.op.bl_idname, text="Flipped", icon_value = icon_get('op_select_islands_flipped')) - + layout = self.layout + layout.separator() + layout.operator(op_select_islands_identical.op.bl_idname, text="Similar", icon_value = icon_get("op_select_islands_identical")) + layout.operator(op_select_islands_overlap.op.bl_idname, text="Overlap", icon_value = icon_get("op_select_islands_overlap")) + layout.operator(op_select_islands_outline.op.bl_idname, text="Bounds", icon_value = icon_get("op_select_islands_outline")) + layout.operator(op_select_islands_flipped.op.bl_idname, text="Flipped", icon_value = icon_get('op_select_islands_flipped')) + def menu_IMAGE_MT_image(self, context): - layout = self.layout - layout.separator() - layout.operator(op_texture_reload_all.op.bl_idname, text="Reload Textures", icon_value = icon_get("op_texture_reload_all")) - layout.operator(op_texel_checker_map.op.bl_idname, text ="Checker Map", icon_value = icon_get("op_texel_checker_map")) - layout.operator(op_texture_preview.op.bl_idname, text = "Preview Texture", icon_value = icon_get("op_texture_preview")); - + layout = self.layout + layout.separator() + layout.operator(op_texture_reload_all.op.bl_idname, text="Reload Textures", icon_value = icon_get("op_texture_reload_all")) + layout.operator(op_texel_checker_map.op.bl_idname, text ="Checker Map", icon_value = icon_get("op_texel_checker_map")) + layout.operator(op_texture_preview.op.bl_idname, text = "Preview Texture", icon_value = icon_get("op_texture_preview")); + def menu_VIEW3D_MT_object(self, context): - self.layout.separator() - self.layout.operator(op_texel_checker_map.op.bl_idname, text ="Checker Map", icon_value = icon_get("op_texel_checker_map")) - self.layout.operator(op_meshtex_create.op.bl_idname, text="Create UV Mesh", icon_value = icon_get("op_meshtex_create")) - + self.layout.separator() + self.layout.operator(op_texel_checker_map.op.bl_idname, text ="Checker Map", icon_value = icon_get("op_texel_checker_map")) + self.layout.operator(op_meshtex_create.op.bl_idname, text="Create UV Mesh", icon_value = icon_get("op_meshtex_create")) + def menu_VIEW3D_MT_mesh_add(self, context): - self.layout.operator(op_meshtex_pattern.op.bl_idname, text="Create Pattern", icon_value = icon_get("op_meshtex_pattern")) + self.layout.operator(op_meshtex_pattern.op.bl_idname, text="Create Pattern", icon_value = icon_get("op_meshtex_pattern")) def menu_VIEW3D_MT_uv_map(self, context): - layout = self.layout - layout.separator() - layout.operator(op_unwrap_edge_peel.op.bl_idname, text="Peel Edge", icon_value = icon_get("op_unwrap_edge_peel")) - layout.operator(op_unwrap_faces_iron.op.bl_idname, text="Iron Faces", icon_value = icon_get("op_unwrap_faces_iron")) - layout.operator(op_smoothing_uv_islands.op.bl_idname, text="UV Smoothing", icon_value = icon_get("op_smoothing_uv_islands")) - + layout = self.layout + layout.separator() + layout.operator(op_unwrap_edge_peel.op.bl_idname, text="Peel Edge", icon_value = icon_get("op_unwrap_edge_peel")) + layout.operator(op_unwrap_faces_iron.op.bl_idname, text="Iron Faces", icon_value = icon_get("op_unwrap_faces_iron")) + layout.operator(op_smoothing_uv_islands.op.bl_idname, text="UV Smoothing", icon_value = icon_get("op_smoothing_uv_islands")) + def menu_VIEW3D_MT_object_context_menu(self, context): - layout = self.layout - layout.separator() - layout.operator(op_meshtex_create.op.bl_idname, text="Create UV Mesh", icon_value = icon_get("op_meshtex_create")) - layout.operator(op_meshtex_trim.op.bl_idname, text="Trim", icon_value = icon_get("op_meshtex_trim")) + layout = self.layout + layout.separator() + layout.operator(op_meshtex_create.op.bl_idname, text="Create UV Mesh", icon_value = icon_get("op_meshtex_create")) + layout.operator(op_meshtex_trim.op.bl_idname, text="Trim", icon_value = icon_get("op_meshtex_trim")) - # Warning about trimmed mesh - if op_meshtex_trim_collapse.is_available(): - layout.operator(op_meshtex_trim_collapse.op.bl_idname, text="Collapse Trim", icon='CANCEL') + # Warning about trimmed mesh + if op_meshtex_trim_collapse.is_available(): + layout.operator(op_meshtex_trim_collapse.op.bl_idname, text="Collapse Trim", icon='CANCEL') - layout.prop(context.scene.texToolsSettings, "meshtexture_wrap", text="Wrap") - layout.operator(op_meshtex_wrap.op.bl_idname, text="Wrap", icon_value = icon_get("op_meshtex_wrap")) + layout.prop(context.scene.texToolsSettings, "meshtexture_wrap", text="Wrap") + layout.operator(op_meshtex_wrap.op.bl_idname, text="Wrap", icon_value = icon_get("op_meshtex_wrap")) classes = ( - UV_OT_op_debug, - UV_OT_op_disable_uv_sync, - UV_OT_op_select_bake_set, - UV_OT_op_select_bake_type, - TexToolsSettings, - UI_PT_Panel_Units, - UI_PT_Panel_Layout, - UI_PT_Panel_Bake, - UI_MT_op_color_dropdown_io, - UI_MT_op_color_dropdown_convert_from, - UI_MT_op_color_dropdown_convert_to, - UV_OT_op_enable_cycles, - UI_PT_Panel_Colors, - UI_PT_Panel_MeshTexture, - VIEW3D_MT_submenu_align, - Panel_Preferences + UV_OT_op_debug, + UV_OT_op_disable_uv_sync, + UV_OT_op_select_bake_set, + UV_OT_op_select_bake_type, + TexToolsSettings, + UI_PT_Panel_Units, + UI_PT_Panel_Layout, + UI_PT_Panel_Bake, + UI_MT_op_color_dropdown_io, + UI_MT_op_color_dropdown_convert_from, + UI_MT_op_color_dropdown_convert_to, + UV_OT_op_enable_cycles, + UI_PT_Panel_Colors, + UI_PT_Panel_MeshTexture, + VIEW3D_MT_submenu_align, + Panel_Preferences ) def register(): - from bpy.utils import register_class - for cls in classes: - register_class(cls) + from bpy.utils import register_class + for cls in classes: + register_class(cls) #Register settings - bpy.types.Scene.texToolsSettings = bpy.props.PointerProperty(type=TexToolsSettings) - - #GUI Utilities - utilities_ui.register() - - # Register Icons - icons = [ - "bake_anti_alias.png", - "bake_obj_cage.png", - "bake_obj_float.png", - "bake_obj_high.png", - "bake_obj_low.png", - "op_align_bottom.png", - "op_align_left.png", - "op_align_right.png", - "op_align_top.png", - "op_bake.png", - "op_bake_explode.png", - "op_color_convert_texture.png", - "op_color_convert_vertex_colors.png", - "op_color_from_directions.png", - "op_color_from_elements.png", - "op_color_from_materials.png", - "op_extend_canvas_open.png", - "op_island_align_edge.png", - "op_island_align_sort_h.png", - "op_island_align_sort_v.png", - "op_island_align_world.png", - "op_island_mirror.png", - "op_island_rotate_90_left.png", - "op_island_rotate_90_right.png", - "op_island_straighten_edge_loops.png", - "op_meshtex_create.png", - "op_meshtex_pattern.png", - "op_meshtex_trim.png", - "op_meshtex_trim_collapse.png", - "op_meshtex_wrap.png", - "op_rectify.png", - "op_select_islands_flipped.png", - "op_select_islands_identical.png", - "op_select_islands_outline.png", - "op_select_islands_overlap.png", - "op_smoothing_uv_islands.png", - "op_texel_checker_map.png", - "op_texture_preview.png", - "op_texture_reload_all.png", - "op_texture_save.png", - "op_texture_open.png", - "op_unwrap_faces_iron.png", - "op_unwrap_edge_peel.png", - "op_uv_crop.png", - "op_uv_fill.png", - "texel_density.png" - ] - for icon in icons: - utilities_ui.icon_register(icon) - - bpy.types.IMAGE_MT_uvs.append(menu_IMAGE_uvs) - bpy.types.IMAGE_MT_select.append(menu_IMAGE_select) - bpy.types.IMAGE_MT_image.append(menu_IMAGE_MT_image) - bpy.types.VIEW3D_MT_object.append(menu_VIEW3D_MT_object) - bpy.types.VIEW3D_MT_add.append(menu_VIEW3D_MT_mesh_add) - bpy.types.VIEW3D_MT_uv_map.append(menu_VIEW3D_MT_uv_map) - bpy.types.VIEW3D_MT_object_context_menu.append(menu_VIEW3D_MT_object_context_menu) - + bpy.types.Scene.texToolsSettings = bpy.props.PointerProperty(type=TexToolsSettings) + + #GUI Utilities + utilities_ui.register() + + # Register Icons + icons = [ + "bake_anti_alias.png", + "bake_obj_cage.png", + "bake_obj_float.png", + "bake_obj_high.png", + "bake_obj_low.png", + "op_align_bottom.png", + "op_align_left.png", + "op_align_right.png", + "op_align_top.png", + "op_bake.png", + "op_bake_explode.png", + "op_color_convert_texture.png", + "op_color_convert_vertex_colors.png", + "op_color_from_directions.png", + "op_color_from_elements.png", + "op_color_from_materials.png", + "op_extend_canvas_open.png", + "op_island_align_edge.png", + "op_island_align_sort_h.png", + "op_island_align_sort_v.png", + "op_island_align_world.png", + "op_island_mirror.png", + "op_island_rotate_90_left.png", + "op_island_rotate_90_right.png", + "op_island_straighten_edge_loops.png", + "op_meshtex_create.png", + "op_meshtex_pattern.png", + "op_meshtex_trim.png", + "op_meshtex_trim_collapse.png", + "op_meshtex_wrap.png", + "op_rectify.png", + "op_select_islands_flipped.png", + "op_select_islands_identical.png", + "op_select_islands_outline.png", + "op_select_islands_overlap.png", + "op_smoothing_uv_islands.png", + "op_texel_checker_map.png", + "op_texture_preview.png", + "op_texture_reload_all.png", + "op_texture_save.png", + "op_texture_open.png", + "op_unwrap_faces_iron.png", + "op_unwrap_edge_peel.png", + "op_uv_crop.png", + "op_uv_fill.png", + "texel_density.png" + ] + for icon in icons: + utilities_ui.icon_register(icon) + + bpy.types.IMAGE_MT_uvs.append(menu_IMAGE_uvs) + bpy.types.IMAGE_MT_select.append(menu_IMAGE_select) + bpy.types.IMAGE_MT_image.append(menu_IMAGE_MT_image) + bpy.types.VIEW3D_MT_object.append(menu_VIEW3D_MT_object) + bpy.types.VIEW3D_MT_add.append(menu_VIEW3D_MT_mesh_add) + bpy.types.VIEW3D_MT_uv_map.append(menu_VIEW3D_MT_uv_map) + bpy.types.VIEW3D_MT_object_context_menu.append(menu_VIEW3D_MT_object_context_menu) + def unregister(): - #GUI Utilities - # utilities_ui.unregister() - - from bpy.utils import unregister_class - for cls in reversed(classes): - unregister_class(cls) - - - #Unregister Settings - del bpy.types.Scene.texToolsSettings - - #handle the keymap - for km, kmi in keymaps: - km.keymap_items.remove(kmi) - keymaps.clear() - - bpy.types.IMAGE_MT_uvs.remove(menu_IMAGE_uvs) - bpy.types.IMAGE_MT_select.remove(menu_IMAGE_select) - bpy.types.IMAGE_MT_image.remove(menu_IMAGE_MT_image) - bpy.types.VIEW3D_MT_object.remove(menu_VIEW3D_MT_object) - bpy.types.VIEW3D_MT_add.remove(menu_VIEW3D_MT_mesh_add) - bpy.types.VIEW3D_MT_uv_map.remove(menu_VIEW3D_MT_uv_map) - bpy.types.VIEW3D_MT_object_context_menu.remove(menu_VIEW3D_MT_object_context_menu) - - + #GUI Utilities + # utilities_ui.unregister() + + from bpy.utils import unregister_class + for cls in reversed(classes): + unregister_class(cls) + + + #Unregister Settings + del bpy.types.Scene.texToolsSettings + + #handle the keymap + for km, kmi in keymaps: + km.keymap_items.remove(kmi) + keymaps.clear() + + bpy.types.IMAGE_MT_uvs.remove(menu_IMAGE_uvs) + bpy.types.IMAGE_MT_select.remove(menu_IMAGE_select) + bpy.types.IMAGE_MT_image.remove(menu_IMAGE_MT_image) + bpy.types.VIEW3D_MT_object.remove(menu_VIEW3D_MT_object) + bpy.types.VIEW3D_MT_add.remove(menu_VIEW3D_MT_mesh_add) + bpy.types.VIEW3D_MT_uv_map.remove(menu_VIEW3D_MT_uv_map) + bpy.types.VIEW3D_MT_object_context_menu.remove(menu_VIEW3D_MT_object_context_menu) + + if __name__ == "__main__": - register() + register() diff --git a/op_align.py b/op_align.py index 6b193a5..ed5ce8b 100644 --- a/op_align.py +++ b/op_align.py @@ -9,119 +9,119 @@ from . import utilities_uv class op(bpy.types.Operator): - bl_idname = "uv.textools_align" - bl_label = "Align" - bl_description = "Align vertices, edges or shells" - bl_options = {'REGISTER', 'UNDO'} - - direction : bpy.props.StringProperty(name="Direction", default="top") + bl_idname = "uv.textools_align" + bl_label = "Align" + bl_description = "Align vertices, edges or shells" + bl_options = {'REGISTER', 'UNDO'} + + direction : bpy.props.StringProperty(name="Direction", default="top") - @classmethod - def poll(cls, context): - if not bpy.context.active_object: - return False + @classmethod + def poll(cls, context): + if not bpy.context.active_object: + return False - #Only in Edit mode - if bpy.context.active_object.mode != 'EDIT': - return False + #Only in Edit mode + if bpy.context.active_object.mode != 'EDIT': + return False - #Only in UV editor mode - if bpy.context.area.type != 'IMAGE_EDITOR': - return False - - #Requires UV map - if not bpy.context.object.data.uv_layers: - return False #self.report({'WARNING'}, "Object must have more than one UV map") + #Only in UV editor mode + if bpy.context.area.type != 'IMAGE_EDITOR': + return False + + #Requires UV map + if not bpy.context.object.data.uv_layers: + return False #self.report({'WARNING'}, "Object must have more than one UV map") - # Not in Synced mode - if bpy.context.scene.tool_settings.use_uv_select_sync: - return False + # Not in Synced mode + if bpy.context.scene.tool_settings.use_uv_select_sync: + return False - return True + return True - def execute(self, context): - - align(context, self.direction) - return {'FINISHED'} + def execute(self, context): + + align(context, self.direction) + return {'FINISHED'} def align(context, direction): - #Store selection - utilities_uv.selection_store() - - if bpy.context.tool_settings.transform_pivot_point != 'CURSOR': - bpy.context.tool_settings.transform_pivot_point = 'CURSOR' - - #B-Mesh - obj = bpy.context.active_object - bm = bmesh.from_edit_mesh(obj.data); - uv_layers = bm.loops.layers.uv.verify(); - - if len(obj.data.uv_layers) == 0: - print("There is no UV channel or UV data set") - return - - # Collect BBox sizes - boundsAll = utilities_uv.getSelectionBBox() - - mode = bpy.context.scene.tool_settings.uv_select_mode - if mode == 'FACE' or mode == 'ISLAND': - print("____ Align Islands") - - #Collect UV islands - islands = utilities_uv.getSelectionIslands() - - for island in islands: - - bpy.ops.uv.select_all(action='DESELECT') - utilities_uv.set_selected_faces(island) - bounds = utilities_uv.getSelectionBBox() - - # print("Island "+str(len(island))+"x faces, delta: "+str(delta.y)) - - if direction == "bottom": - delta = boundsAll['min'] - bounds['min'] - bpy.ops.transform.translate(value=(0, delta.y, 0)) - elif direction == "top": - delta = boundsAll['max'] - bounds['max'] - bpy.ops.transform.translate(value=(0, delta.y, 0)) - elif direction == "left": - delta = boundsAll['min'] - bounds['min'] - bpy.ops.transform.translate(value=(delta.x, 0, 0)) - elif direction == "right": - delta = boundsAll['max'] - bounds['max'] - bpy.ops.transform.translate(value=(delta.x, 0, 0)) - else: - print("Unkown direction: "+str(direction)) - - - elif mode == 'EDGE' or mode == 'VERTEX': - print("____ Align Verts") - - for f in bm.faces: - if f.select: - for l in f.loops: - luv = l[uv_layers] - if luv.select: - # print("Idx: "+str(luv.uv)) - if direction == "top": - luv.uv[1] = boundsAll['max'].y - elif direction == "bottom": - luv.uv[1] = boundsAll['min'].y - elif direction == "left": - luv.uv[0] = boundsAll['min'].x - elif direction == "right": - luv.uv[0] = boundsAll['max'].x - - - bmesh.update_edit_mesh(obj.data) - - #Restore selection - utilities_uv.selection_restore() + #Store selection + utilities_uv.selection_store() + + if bpy.context.tool_settings.transform_pivot_point != 'CURSOR': + bpy.context.tool_settings.transform_pivot_point = 'CURSOR' + + #B-Mesh + obj = bpy.context.active_object + bm = bmesh.from_edit_mesh(obj.data); + uv_layers = bm.loops.layers.uv.verify(); + + if len(obj.data.uv_layers) == 0: + print("There is no UV channel or UV data set") + return + + # Collect BBox sizes + boundsAll = utilities_uv.getSelectionBBox() + + mode = bpy.context.scene.tool_settings.uv_select_mode + if mode == 'FACE' or mode == 'ISLAND': + print("____ Align Islands") + + #Collect UV islands + islands = utilities_uv.getSelectionIslands() + + for island in islands: + + bpy.ops.uv.select_all(action='DESELECT') + utilities_uv.set_selected_faces(island) + bounds = utilities_uv.getSelectionBBox() + + # print("Island "+str(len(island))+"x faces, delta: "+str(delta.y)) + + if direction == "bottom": + delta = boundsAll['min'] - bounds['min'] + bpy.ops.transform.translate(value=(0, delta.y, 0)) + elif direction == "top": + delta = boundsAll['max'] - bounds['max'] + bpy.ops.transform.translate(value=(0, delta.y, 0)) + elif direction == "left": + delta = boundsAll['min'] - bounds['min'] + bpy.ops.transform.translate(value=(delta.x, 0, 0)) + elif direction == "right": + delta = boundsAll['max'] - bounds['max'] + bpy.ops.transform.translate(value=(delta.x, 0, 0)) + else: + print("Unkown direction: "+str(direction)) + + + elif mode == 'EDGE' or mode == 'VERTEX': + print("____ Align Verts") + + for f in bm.faces: + if f.select: + for l in f.loops: + luv = l[uv_layers] + if luv.select: + # print("Idx: "+str(luv.uv)) + if direction == "top": + luv.uv[1] = boundsAll['max'].y + elif direction == "bottom": + luv.uv[1] = boundsAll['min'].y + elif direction == "left": + luv.uv[0] = boundsAll['min'].x + elif direction == "right": + luv.uv[0] = boundsAll['max'].x + + + bmesh.update_edit_mesh(obj.data) + + #Restore selection + utilities_uv.selection_restore() bpy.utils.register_class(op) diff --git a/op_bake.py b/op_bake.py index 640c4bc..8f08f77 100644 --- a/op_bake.py +++ b/op_bake.py @@ -13,569 +13,569 @@ from . import utilities_bake as ub #Use shorthand ub = utitlites_bake # Notes: https://docs.blender.org/manual/en/dev/render/blender_render/bake.html modes={ - 'normal_tangent': ub.BakeMode('', type='NORMAL', color=(0.5, 0.5, 1, 1), use_project=True), - 'normal_object': ub.BakeMode('', type='NORMAL', color=(0.5, 0.5, 1, 1), normal_space='OBJECT' ), - 'cavity': ub.BakeMode('bake_cavity', type='EMIT', setVColor=ub.setup_vertex_color_dirty), - 'paint_base': ub.BakeMode('bake_paint_base', type='EMIT'), - 'dust': ub.BakeMode('bake_dust', type='EMIT', setVColor=ub.setup_vertex_color_dirty), - 'id_element': ub.BakeMode('bake_vertex_color',type='EMIT', setVColor=ub.setup_vertex_color_id_element), - 'id_material': ub.BakeMode('bake_vertex_color',type='EMIT', setVColor=ub.setup_vertex_color_id_material), - 'selection': ub.BakeMode('bake_vertex_color',type='EMIT', color=(0, 0, 0, 1), setVColor=ub.setup_vertex_color_selection), - 'diffuse': ub.BakeMode('', type='DIFFUSE'), - # 'displacment': ub.BakeMode('', type='DISPLACEMENT', use_project=True, color=(0, 0, 0, 1), engine='CYCLES'), - 'ao': ub.BakeMode('', type='AO', color=(1, 1, 1, 0), params=["bake_samples"], engine='CYCLES'), - 'ao_legacy': ub.BakeMode('', type='AO', params=["bake_samples"], engine='CYCLES'), - 'position': ub.BakeMode('bake_position', type='EMIT'), - 'curvature': ub.BakeMode('', type='NORMAL', use_project=True, params=["bake_curvature_size"], composite="curvature"), - 'wireframe': ub.BakeMode('bake_wireframe', type='EMIT', color=(0, 0, 0, 1), params=["bake_wireframe_size"]) + 'normal_tangent': ub.BakeMode('', type='NORMAL', color=(0.5, 0.5, 1, 1), use_project=True), + 'normal_object': ub.BakeMode('', type='NORMAL', color=(0.5, 0.5, 1, 1), normal_space='OBJECT' ), + 'cavity': ub.BakeMode('bake_cavity', type='EMIT', setVColor=ub.setup_vertex_color_dirty), + 'paint_base': ub.BakeMode('bake_paint_base', type='EMIT'), + 'dust': ub.BakeMode('bake_dust', type='EMIT', setVColor=ub.setup_vertex_color_dirty), + 'id_element': ub.BakeMode('bake_vertex_color',type='EMIT', setVColor=ub.setup_vertex_color_id_element), + 'id_material': ub.BakeMode('bake_vertex_color',type='EMIT', setVColor=ub.setup_vertex_color_id_material), + 'selection': ub.BakeMode('bake_vertex_color',type='EMIT', color=(0, 0, 0, 1), setVColor=ub.setup_vertex_color_selection), + 'diffuse': ub.BakeMode('', type='DIFFUSE'), + # 'displacment': ub.BakeMode('', type='DISPLACEMENT', use_project=True, color=(0, 0, 0, 1), engine='CYCLES'), + 'ao': ub.BakeMode('', type='AO', color=(1, 1, 1, 0), params=["bake_samples"], engine='CYCLES'), + 'ao_legacy': ub.BakeMode('', type='AO', params=["bake_samples"], engine='CYCLES'), + 'position': ub.BakeMode('bake_position', type='EMIT'), + 'curvature': ub.BakeMode('', type='NORMAL', use_project=True, params=["bake_curvature_size"], composite="curvature"), + 'wireframe': ub.BakeMode('bake_wireframe', type='EMIT', color=(0, 0, 0, 1), params=["bake_wireframe_size"]) } if hasattr(bpy.types,"ShaderNodeBevel"): - # Has newer bevel shader (2.7 nightly build series) - modes['bevel_mask'] = ub.BakeMode('bake_bevel_mask', type='EMIT', color=(0, 0, 0, 1), params=["bake_bevel_samples","bake_bevel_size"]) - modes['normal_tangent_bevel'] = ub.BakeMode('bake_bevel_normal', type='NORMAL', color=(0.5, 0.5, 1, 1), params=["bake_bevel_samples","bake_bevel_size"]) - modes['normal_object_bevel'] = ub.BakeMode('bake_bevel_normal', type='NORMAL', color=(0.5, 0.5, 1, 1), normal_space='OBJECT', params=["bake_bevel_samples","bake_bevel_size"]) + # Has newer bevel shader (2.7 nightly build series) + modes['bevel_mask'] = ub.BakeMode('bake_bevel_mask', type='EMIT', color=(0, 0, 0, 1), params=["bake_bevel_samples","bake_bevel_size"]) + modes['normal_tangent_bevel'] = ub.BakeMode('bake_bevel_normal', type='NORMAL', color=(0.5, 0.5, 1, 1), params=["bake_bevel_samples","bake_bevel_size"]) + modes['normal_object_bevel'] = ub.BakeMode('bake_bevel_normal', type='NORMAL', color=(0.5, 0.5, 1, 1), normal_space='OBJECT', params=["bake_bevel_samples","bake_bevel_size"]) class op(bpy.types.Operator): - bl_idname = "uv.textools_bake" - bl_label = "Bake" - bl_description = "Bake selected objects" - - @classmethod - def poll(cls, context): - if len(settings.sets) == 0: - return False - return True - - def execute(self, context): - bake_mode = utilities_ui.get_bake_mode() - - if bake_mode not in modes: - self.report({'ERROR_INVALID_INPUT'}, "Uknown mode '{}' only available: '{}'".format(bake_mode, ", ".join(modes.keys() )) ) - return - - # Store Selection - selected_objects = [obj for obj in bpy.context.selected_objects] - active_object = bpy.context.view_layer.objects.active - ub.store_bake_settings() - - # Render sets - bake( - self = self, - mode = bake_mode, - size = bpy.context.scene.texToolsSettings.size, - - bake_single = bpy.context.scene.texToolsSettings.bake_force_single, - sampling_scale = int(bpy.context.scene.texToolsSettings.bake_sampling), - samples = bpy.context.scene.texToolsSettings.bake_samples, - ray_distance = bpy.context.scene.texToolsSettings.bake_ray_distance - ) - - # Restore selection - ub.restore_bake_settings() - bpy.ops.object.select_all(action='DESELECT') - for obj in selected_objects: - obj.select_set( state = True, view_layer = None) - if active_object: - bpy.context.view_layer.objects.active = active_object - - return {'FINISHED'} + bl_idname = "uv.textools_bake" + bl_label = "Bake" + bl_description = "Bake selected objects" + + @classmethod + def poll(cls, context): + if len(settings.sets) == 0: + return False + return True + + def execute(self, context): + bake_mode = utilities_ui.get_bake_mode() + + if bake_mode not in modes: + self.report({'ERROR_INVALID_INPUT'}, "Uknown mode '{}' only available: '{}'".format(bake_mode, ", ".join(modes.keys() )) ) + return + + # Store Selection + selected_objects = [obj for obj in bpy.context.selected_objects] + active_object = bpy.context.view_layer.objects.active + ub.store_bake_settings() + + # Render sets + bake( + self = self, + mode = bake_mode, + size = bpy.context.scene.texToolsSettings.size, + + bake_single = bpy.context.scene.texToolsSettings.bake_force_single, + sampling_scale = int(bpy.context.scene.texToolsSettings.bake_sampling), + samples = bpy.context.scene.texToolsSettings.bake_samples, + ray_distance = bpy.context.scene.texToolsSettings.bake_ray_distance + ) + + # Restore selection + ub.restore_bake_settings() + bpy.ops.object.select_all(action='DESELECT') + for obj in selected_objects: + obj.select_set( state = True, view_layer = None) + if active_object: + bpy.context.view_layer.objects.active = active_object + + return {'FINISHED'} def bake(self, mode, size, bake_single, sampling_scale, samples, ray_distance): - print("Bake '{}'".format(mode)) + print("Bake '{}'".format(mode)) - bpy.context.scene.render.engine = modes[mode].engine #Switch render engine + bpy.context.scene.render.engine = modes[mode].engine #Switch render engine - # Disable edit mode - if bpy.context.view_layer.objects.active != None and bpy.context.object.mode != 'OBJECT': - bpy.ops.object.mode_set(mode='OBJECT') + # Disable edit mode + if bpy.context.view_layer.objects.active != None and bpy.context.object.mode != 'OBJECT': + bpy.ops.object.mode_set(mode='OBJECT') - ub.store_materials_clear() + ub.store_materials_clear() - # Get the baking sets / pairs - sets = settings.sets + # Get the baking sets / pairs + sets = settings.sets - render_width = sampling_scale * size[0] - render_height = sampling_scale * size[1] - - for s in range(0,len(sets)): - set = sets[s] - - # Get image name - name_texture = "{}_{}".format(set.name, mode) - if bake_single: - name_texture = "{}_{}".format(sets[0].name, mode)# In Single mode bake into same texture - path = bpy.path.abspath("//{}.tga".format(name_texture)) - - # Requires 1+ low poly objects - if len(set.objects_low) == 0: - self.report({'ERROR_INVALID_INPUT'}, "No low poly object as part of the '{}' set".format(set.name) ) - return - - # Check for UV maps - for obj in set.objects_low: - if not obj.data.uv_layers or len(obj.data.uv_layers) == 0: - self.report({'ERROR_INVALID_INPUT'}, "No UV map available for '{}'".format(obj.name)) - return - - # Check for cage inconsistencies - if len(set.objects_cage) > 0 and (len(set.objects_low) != len(set.objects_cage)): - self.report({'ERROR_INVALID_INPUT'}, "{}x cage objects do not match {}x low poly objects for '{}'".format(len(set.objects_cage), len(set.objects_low), obj.name)) - return - - # Get Materials - material_loaded = get_material(mode) - material_empty = None - if "TT_bake_node" in bpy.data.materials: - material_empty = bpy.data.materials["TT_bake_node"] - else: - material_empty = bpy.data.materials.new(name="TT_bake_node") - - - # Assign Materials to Objects - if (len(set.objects_high) + len(set.objects_float)) == 0: - # Low poly bake: Assign material to lowpoly - for obj in set.objects_low: - assign_vertex_color(mode, obj) - assign_material(mode, obj, material_loaded, material_empty) - else: - # High to low poly: Low poly require empty material to bake into image - for obj in set.objects_low: - assign_material(mode, obj, None, material_empty) - - # Assign material to highpoly - for obj in (set.objects_high+set.objects_float): - assign_vertex_color(mode, obj) - assign_material(mode, obj, material_loaded) - - - # Setup Image - is_clear = (not bake_single) or (bake_single and s==0) - image = setup_image(mode, name_texture, render_width, render_height, path, is_clear) - - # Assign bake node to Material - setup_image_bake_node(set.objects_low[0], image) - - - print("Bake '{}' = {}".format(set.name, path)) - - # Hide all cage objects i nrender - for obj_cage in set.objects_cage: - obj_cage.hide_render = True - - # Bake each low poly object in this set - for i in range(len(set.objects_low)): - obj_low = set.objects_low[i] - obj_cage = None if i >= len(set.objects_cage) else set.objects_cage[i] - - # Disable hide render - obj_low.hide_render = False - - bpy.ops.object.select_all(action='DESELECT') - obj_low.select_set( state = True, view_layer = None) - bpy.context.view_layer.objects.active = obj_low - - if modes[mode].engine == 'BLENDER_EEVEE': - # Assign image to texture faces - bpy.ops.object.mode_set(mode='EDIT') - bpy.ops.mesh.select_all(action='SELECT') - - for area in bpy.context.screen.areas: - if area.type == 'IMAGE_EDITOR': - area.spaces[0].image = image - # bpy.data.screens['UV Editing'].areas[1].spaces[0].image = image - - - bpy.ops.object.mode_set(mode='OBJECT') - - for obj_high in (set.objects_high): - obj_high.select_set( state = True, view_layer = None) - cycles_bake( - mode, - bpy.context.scene.texToolsSettings.padding, - sampling_scale, - samples, - ray_distance, - len(set.objects_high) > 0, - obj_cage - ) - - # Bake Floaters seperate bake - if len(set.objects_float) > 0: - bpy.ops.object.select_all(action='DESELECT') - for obj_high in (set.objects_float): - obj_high.select_set( state = True, view_layer = None) - obj_low.select_set( state = True, view_layer = None) - - cycles_bake( - mode, - 0, - sampling_scale, - samples, - ray_distance, - len(set.objects_float) > 0, - obj_cage - ) - - # Set background image (CYCLES & BLENDER_EEVEE) - for area in bpy.context.screen.areas: - if area.type == 'IMAGE_EDITOR': - area.spaces[0].image = image - - # Restore renderable for cage objects - for obj_cage in set.objects_cage: - obj_cage.hide_render = False - - - # Downsample image? - if not bake_single or (bake_single and s == len(sets)-1): - # When baking single, only downsample on last bake - if render_width != size[0] or render_height != size[1]: - image.scale(size[0],size[1]) - - # Apply composite nodes on final image result - if modes[mode].composite: - apply_composite(image, modes[mode].composite, bpy.context.scene.texToolsSettings.bake_curvature_size) - - # image.save() + render_width = sampling_scale * size[0] + render_height = sampling_scale * size[1] + + for s in range(0,len(sets)): + set = sets[s] + + # Get image name + name_texture = "{}_{}".format(set.name, mode) + if bake_single: + name_texture = "{}_{}".format(sets[0].name, mode)# In Single mode bake into same texture + path = bpy.path.abspath("//{}.tga".format(name_texture)) + + # Requires 1+ low poly objects + if len(set.objects_low) == 0: + self.report({'ERROR_INVALID_INPUT'}, "No low poly object as part of the '{}' set".format(set.name) ) + return + + # Check for UV maps + for obj in set.objects_low: + if not obj.data.uv_layers or len(obj.data.uv_layers) == 0: + self.report({'ERROR_INVALID_INPUT'}, "No UV map available for '{}'".format(obj.name)) + return + + # Check for cage inconsistencies + if len(set.objects_cage) > 0 and (len(set.objects_low) != len(set.objects_cage)): + self.report({'ERROR_INVALID_INPUT'}, "{}x cage objects do not match {}x low poly objects for '{}'".format(len(set.objects_cage), len(set.objects_low), obj.name)) + return + + # Get Materials + material_loaded = get_material(mode) + material_empty = None + if "TT_bake_node" in bpy.data.materials: + material_empty = bpy.data.materials["TT_bake_node"] + else: + material_empty = bpy.data.materials.new(name="TT_bake_node") + + + # Assign Materials to Objects + if (len(set.objects_high) + len(set.objects_float)) == 0: + # Low poly bake: Assign material to lowpoly + for obj in set.objects_low: + assign_vertex_color(mode, obj) + assign_material(mode, obj, material_loaded, material_empty) + else: + # High to low poly: Low poly require empty material to bake into image + for obj in set.objects_low: + assign_material(mode, obj, None, material_empty) + + # Assign material to highpoly + for obj in (set.objects_high+set.objects_float): + assign_vertex_color(mode, obj) + assign_material(mode, obj, material_loaded) + + + # Setup Image + is_clear = (not bake_single) or (bake_single and s==0) + image = setup_image(mode, name_texture, render_width, render_height, path, is_clear) + + # Assign bake node to Material + setup_image_bake_node(set.objects_low[0], image) + + + print("Bake '{}' = {}".format(set.name, path)) + + # Hide all cage objects i nrender + for obj_cage in set.objects_cage: + obj_cage.hide_render = True + + # Bake each low poly object in this set + for i in range(len(set.objects_low)): + obj_low = set.objects_low[i] + obj_cage = None if i >= len(set.objects_cage) else set.objects_cage[i] + + # Disable hide render + obj_low.hide_render = False + + bpy.ops.object.select_all(action='DESELECT') + obj_low.select_set( state = True, view_layer = None) + bpy.context.view_layer.objects.active = obj_low + + if modes[mode].engine == 'BLENDER_EEVEE': + # Assign image to texture faces + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.select_all(action='SELECT') + + for area in bpy.context.screen.areas: + if area.type == 'IMAGE_EDITOR': + area.spaces[0].image = image + # bpy.data.screens['UV Editing'].areas[1].spaces[0].image = image + + + bpy.ops.object.mode_set(mode='OBJECT') + + for obj_high in (set.objects_high): + obj_high.select_set( state = True, view_layer = None) + cycles_bake( + mode, + bpy.context.scene.texToolsSettings.padding, + sampling_scale, + samples, + ray_distance, + len(set.objects_high) > 0, + obj_cage + ) + + # Bake Floaters seperate bake + if len(set.objects_float) > 0: + bpy.ops.object.select_all(action='DESELECT') + for obj_high in (set.objects_float): + obj_high.select_set( state = True, view_layer = None) + obj_low.select_set( state = True, view_layer = None) + + cycles_bake( + mode, + 0, + sampling_scale, + samples, + ray_distance, + len(set.objects_float) > 0, + obj_cage + ) + + # Set background image (CYCLES & BLENDER_EEVEE) + for area in bpy.context.screen.areas: + if area.type == 'IMAGE_EDITOR': + area.spaces[0].image = image + + # Restore renderable for cage objects + for obj_cage in set.objects_cage: + obj_cage.hide_render = False + + + # Downsample image? + if not bake_single or (bake_single and s == len(sets)-1): + # When baking single, only downsample on last bake + if render_width != size[0] or render_height != size[1]: + image.scale(size[0],size[1]) + + # Apply composite nodes on final image result + if modes[mode].composite: + apply_composite(image, modes[mode].composite, bpy.context.scene.texToolsSettings.bake_curvature_size) + + # image.save() - # Restore non node materials - ub.restore_materials() + # Restore non node materials + ub.restore_materials() def apply_composite(image, scene_name, size): - previous_scene = bpy.context.window.scene + previous_scene = bpy.context.window.scene - # Get Scene with compositing nodes - scene = None - if scene_name in bpy.data.scenes: - scene = bpy.data.scenes[scene_name] - else: - path = os.path.join(os.path.dirname(__file__), "resources/compositing.blend")+"\\Scene\\" - bpy.ops.wm.append(filename=scene_name, directory=path, link=False, autoselect=False) - scene = bpy.data.scenes[scene_name] + # Get Scene with compositing nodes + scene = None + if scene_name in bpy.data.scenes: + scene = bpy.data.scenes[scene_name] + else: + path = os.path.join(os.path.dirname(__file__), "resources/compositing.blend")+"\\Scene\\" + bpy.ops.wm.append(filename=scene_name, directory=path, link=False, autoselect=False) + scene = bpy.data.scenes[scene_name] - if scene: - # Switch scene - bpy.context.window.scene = scene + if scene: + # Switch scene + bpy.context.window.scene = scene - #Setup composite nodes for Curvature - if "Image" in scene.node_tree.nodes: - scene.node_tree.nodes["Image"].image = image + #Setup composite nodes for Curvature + if "Image" in scene.node_tree.nodes: + scene.node_tree.nodes["Image"].image = image - if "Offset" in scene.node_tree.nodes: - scene.node_tree.nodes["Offset"].outputs[0].default_value = size - print("Assign offset: {}".format(scene.node_tree.nodes["Offset"].outputs[0].default_value)) + if "Offset" in scene.node_tree.nodes: + scene.node_tree.nodes["Offset"].outputs[0].default_value = size + print("Assign offset: {}".format(scene.node_tree.nodes["Offset"].outputs[0].default_value)) - # Render image - bpy.ops.render.render(use_viewport=False) - + # Render image + bpy.ops.render.render(use_viewport=False) + - # Get last images of viewer node and render result - image_viewer_node = get_last_item("Viewer Node", bpy.data.images) - image_render_result = get_last_item("Render Result", bpy.data.images) + # Get last images of viewer node and render result + image_viewer_node = get_last_item("Viewer Node", bpy.data.images) + image_render_result = get_last_item("Render Result", bpy.data.images) - #Copy pixels - image.pixels = image_viewer_node.pixels[:] - image.update() + #Copy pixels + image.pixels = image_viewer_node.pixels[:] + image.update() - if image_viewer_node: - bpy.data.images.remove(image_viewer_node) - if image_render_result: - bpy.data.images.remove(image_render_result) + if image_viewer_node: + bpy.data.images.remove(image_viewer_node) + if image_render_result: + bpy.data.images.remove(image_render_result) - #Restore scene & remove other scene - bpy.context.window.scene = previous_scene - - # Delete compositing scene - bpy.data.scenes.remove(scene) + #Restore scene & remove other scene + bpy.context.window.scene = previous_scene + + # Delete compositing scene + bpy.data.scenes.remove(scene) def get_last_item(key_name, collection): - # bpy.data.images - # Get last image of a series, e.g. .001, .002, 003 - keys = [] - for item in collection: - if key_name in item.name: - keys.append(item.name) + # bpy.data.images + # Get last image of a series, e.g. .001, .002, 003 + keys = [] + for item in collection: + if key_name in item.name: + keys.append(item.name) - print("Search for {}x : '{}'".format(len(keys), ",".join(keys) ) ) + print("Search for {}x : '{}'".format(len(keys), ",".join(keys) ) ) - if len(keys) > 0: - return collection[keys[-1]] + if len(keys) > 0: + return collection[keys[-1]] - return None + return None def setup_image(mode, name, width, height, path, is_clear): - image = None + image = None - print("Path "+path) - if name in bpy.data.images: - image = bpy.data.images[name] - if image.source == 'FILE': - # Clear image if it was deleted outside - if not os.path.isfile(image.filepath): - image.user_clear() - bpy.data.images.remove(image) - # bpy.data.images[name].update() + print("Path "+path) + if name in bpy.data.images: + image = bpy.data.images[name] + if image.source == 'FILE': + # Clear image if it was deleted outside + if not os.path.isfile(image.filepath): + image.user_clear() + bpy.data.images.remove(image) + # bpy.data.images[name].update() - # if bpy.data.images[name].has_data == False: - + # if bpy.data.images[name].has_data == False: + - # Previous image does not have data, remove first - # print("Image pointer exists but no data "+name) - # image = bpy.data.images[name] - # image.update() - # image.generated_height = height - # bpy.data.images.remove(bpy.data.images[name]) + # Previous image does not have data, remove first + # print("Image pointer exists but no data "+name) + # image = bpy.data.images[name] + # image.update() + # image.generated_height = height + # bpy.data.images.remove(bpy.data.images[name]) - if name not in bpy.data.images: - # Create new image with 32 bit float - is_float_32 = bpy.context.preferences.addons["textools"].preferences.bake_32bit_float == '32' - image = bpy.data.images.new(name, width=width, height=height, float_buffer=is_float_32) - if "_normal_" in image.name: - image.colorspace_settings.name = 'Non-Color' - else: - image.colorspace_settings.name = 'sRGB' + if name not in bpy.data.images: + # Create new image with 32 bit float + is_float_32 = bpy.context.preferences.addons["textools"].preferences.bake_32bit_float == '32' + image = bpy.data.images.new(name, width=width, height=height, float_buffer=is_float_32) + if "_normal_" in image.name: + image.colorspace_settings.name = 'Non-Color' + else: + image.colorspace_settings.name = 'sRGB' - else: - # Reuse existing Image - image = bpy.data.images[name] - # Reisze? - if image.size[0] != width or image.size[1] != height or image.generated_width != width or image.generated_height != height: - image.generated_width = width - image.generated_height = height - image.scale(width, height) + else: + # Reuse existing Image + image = bpy.data.images[name] + # Reisze? + if image.size[0] != width or image.size[1] != height or image.generated_width != width or image.generated_height != height: + image.generated_width = width + image.generated_height = height + image.scale(width, height) - # Fill with plain color - if is_clear: - image.generated_color = modes[mode].color - image.generated_type = 'BLANK' + # Fill with plain color + if is_clear: + image.generated_color = modes[mode].color + image.generated_type = 'BLANK' - image.file_format = 'TARGA' + image.file_format = 'TARGA' - # TODO: Verify that the path exists - # image.filepath_raw = path + # TODO: Verify that the path exists + # image.filepath_raw = path - return image + return image def setup_image_bake_node(obj, image): - if len(obj.data.materials) <= 0: - print("ERROR, need spare material to setup active image texture to bake!!!") - else: - for slot in obj.material_slots: - if slot.material: - if(slot.material.use_nodes == False): - slot.material.use_nodes = True + if len(obj.data.materials) <= 0: + print("ERROR, need spare material to setup active image texture to bake!!!") + else: + for slot in obj.material_slots: + if slot.material: + if(slot.material.use_nodes == False): + slot.material.use_nodes = True - # Assign bake node - tree = slot.material.node_tree - node = None - if "bake" in tree.nodes: - node = tree.nodes["bake"] - else: - node = tree.nodes.new("ShaderNodeTexImage") - node.name = "bake" - node.select = True - node.image = image - tree.nodes.active = node + # Assign bake node + tree = slot.material.node_tree + node = None + if "bake" in tree.nodes: + node = tree.nodes["bake"] + else: + node = tree.nodes.new("ShaderNodeTexImage") + node.name = "bake" + node.select = True + node.image = image + tree.nodes.active = node def assign_vertex_color(mode, obj): - if modes[mode].setVColor: - modes[mode].setVColor(obj) + if modes[mode].setVColor: + modes[mode].setVColor(obj) def assign_material(mode, obj, material_bake=None, material_empty=None): - ub.store_materials(obj) + ub.store_materials(obj) - bpy.context.view_layer.objects.active = obj - obj.select_set( state = True, view_layer = None) + bpy.context.view_layer.objects.active = obj + obj.select_set( state = True, view_layer = None) - # Select All faces - bpy.ops.object.mode_set(mode='EDIT') - bm = bmesh.from_edit_mesh(bpy.context.active_object.data); - faces = [face for face in bm.faces if face.select] - bpy.ops.mesh.select_all(action='SELECT') + # Select All faces + bpy.ops.object.mode_set(mode='EDIT') + bm = bmesh.from_edit_mesh(bpy.context.active_object.data); + faces = [face for face in bm.faces if face.select] + bpy.ops.mesh.select_all(action='SELECT') - if material_bake: - # Setup properties of bake materials - if mode == 'wireframe': - if "Value" in material_bake.node_tree.nodes: - material_bake.node_tree.nodes["Value"].outputs[0].default_value = bpy.context.scene.texToolsSettings.bake_wireframe_size - if mode == 'bevel_mask': - if "Bevel" in material_bake.node_tree.nodes: - material_bake.node_tree.nodes["Bevel"].inputs[0].default_value = bpy.context.scene.texToolsSettings.bake_bevel_size - material_bake.node_tree.nodes["Bevel"].samples = bpy.context.scene.texToolsSettings.bake_bevel_samples - if mode == 'normal_tangent_bevel': - if "Bevel" in material_bake.node_tree.nodes: - material_bake.node_tree.nodes["Bevel"].inputs[0].default_value = bpy.context.scene.texToolsSettings.bake_bevel_size - material_bake.node_tree.nodes["Bevel"].samples = bpy.context.scene.texToolsSettings.bake_bevel_samples - if mode == 'normal_object_bevel': - if "Bevel" in material_bake.node_tree.nodes: - material_bake.node_tree.nodes["Bevel"].inputs[0].default_value = bpy.context.scene.texToolsSettings.bake_bevel_size - material_bake.node_tree.nodes["Bevel"].samples = bpy.context.scene.texToolsSettings.bake_bevel_samples + if material_bake: + # Setup properties of bake materials + if mode == 'wireframe': + if "Value" in material_bake.node_tree.nodes: + material_bake.node_tree.nodes["Value"].outputs[0].default_value = bpy.context.scene.texToolsSettings.bake_wireframe_size + if mode == 'bevel_mask': + if "Bevel" in material_bake.node_tree.nodes: + material_bake.node_tree.nodes["Bevel"].inputs[0].default_value = bpy.context.scene.texToolsSettings.bake_bevel_size + material_bake.node_tree.nodes["Bevel"].samples = bpy.context.scene.texToolsSettings.bake_bevel_samples + if mode == 'normal_tangent_bevel': + if "Bevel" in material_bake.node_tree.nodes: + material_bake.node_tree.nodes["Bevel"].inputs[0].default_value = bpy.context.scene.texToolsSettings.bake_bevel_size + material_bake.node_tree.nodes["Bevel"].samples = bpy.context.scene.texToolsSettings.bake_bevel_samples + if mode == 'normal_object_bevel': + if "Bevel" in material_bake.node_tree.nodes: + material_bake.node_tree.nodes["Bevel"].inputs[0].default_value = bpy.context.scene.texToolsSettings.bake_bevel_size + material_bake.node_tree.nodes["Bevel"].samples = bpy.context.scene.texToolsSettings.bake_bevel_samples - # Don't apply in diffuse mode - if mode != 'diffuse': - if material_bake: - # Override with material_bake - if len(obj.material_slots) == 0: - obj.data.materials.append(material_bake) + # Don't apply in diffuse mode + if mode != 'diffuse': + if material_bake: + # Override with material_bake + if len(obj.material_slots) == 0: + obj.data.materials.append(material_bake) - else: - obj.material_slots[0].material = material_bake - obj.active_material_index = 0 - bpy.ops.object.material_slot_assign() + else: + obj.material_slots[0].material = material_bake + obj.active_material_index = 0 + bpy.ops.object.material_slot_assign() - elif material_empty: - #Assign material_empty if no material available - if len(obj.material_slots) == 0: - obj.data.materials.append(material_empty) + elif material_empty: + #Assign material_empty if no material available + if len(obj.material_slots) == 0: + obj.data.materials.append(material_empty) - else: # not obj.material_slots[0].material: - obj.material_slots[0].material = material_empty - obj.active_material_index = 0 - bpy.ops.object.material_slot_assign() + else: # not obj.material_slots[0].material: + obj.material_slots[0].material = material_empty + obj.active_material_index = 0 + bpy.ops.object.material_slot_assign() - # Restore Face selection - bpy.ops.mesh.select_all(action='DESELECT') - for face in faces: - face.select = True + # Restore Face selection + bpy.ops.mesh.select_all(action='DESELECT') + for face in faces: + face.select = True - bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.mode_set(mode='OBJECT') - + - + def get_material(mode): - + - if modes[mode].material == "": - return None # No material setup requires + if modes[mode].material == "": + return None # No material setup requires - # Find or load material - name = modes[mode].material - path = os.path.join(os.path.dirname(__file__), "resources/materials.blend")+"\\Material\\" - if "bevel" in mode: - path = os.path.join(os.path.dirname(__file__), "resources/materials_2.80.blend")+"\\Material\\" - - print("Get mat {}\n{}".format(mode, path)) + # Find or load material + name = modes[mode].material + path = os.path.join(os.path.dirname(__file__), "resources/materials.blend")+"\\Material\\" + if "bevel" in mode: + path = os.path.join(os.path.dirname(__file__), "resources/materials_2.80.blend")+"\\Material\\" + + print("Get mat {}\n{}".format(mode, path)) - if bpy.data.materials.get(name) is None: - print("Material not yet loaded: "+mode) - bpy.ops.wm.append(filename=name, directory=path, link=False, autoselect=False) + if bpy.data.materials.get(name) is None: + print("Material not yet loaded: "+mode) + bpy.ops.wm.append(filename=name, directory=path, link=False, autoselect=False) - return bpy.data.materials.get(name) + return bpy.data.materials.get(name) def cycles_bake(mode, padding, sampling_scale, samples, ray_distance, is_multi, obj_cage): - - - # if modes[mode].engine == 'BLENDER_EEVEE': - # # Snippet: https://gist.github.com/AndrewRayCode/760c4634a77551827de41ed67585064b - # bpy.context.scene.render.bake_margin = padding - - # # AO Settings - # bpy.context.scene.render.bake_type = modes[mode].type - # bpy.context.scene.render.use_bake_normalize = True - - # if modes[mode].type == 'AO': - # bpy.context.scene.world.light_settings.use_ambient_occlusion = True - # bpy.context.scene.world.light_settings.gather_method = 'RAYTRACE' - # bpy.context.scene.world.light_settings.samples = samples - - # bpy.context.scene.render.use_bake_selected_to_active = is_multi - # bpy.context.scene.render.bake_distance = ray_distance - # bpy.context.scene.render.use_bake_clear = False - - # bpy.ops.object.bake_image() - - - if modes[mode].engine == 'CYCLES' or modes[mode].engine == 'BLENDER_EEVEE' : - - if modes[mode].normal_space == 'OBJECT': - #See: https://twitter.com/Linko_3D/status/963066705584054272 - bpy.context.scene.render.bake.normal_r = 'POS_X' - bpy.context.scene.render.bake.normal_g = 'POS_Z' - bpy.context.scene.render.bake.normal_b = 'NEG_Y' - - elif modes[mode].normal_space == 'TANGENT': - bpy.context.scene.render.bake.normal_r = 'POS_X' - bpy.context.scene.render.bake.normal_b = 'POS_Z' - # Adjust Y swizzle from Addon preferences - swizzle_y = bpy.context.preferences.addons["textools"].preferences.swizzle_y_coordinate - if swizzle_y == 'Y-': - bpy.context.scene.render.bake.normal_g = 'NEG_Y' - elif swizzle_y == 'Y+': - bpy.context.scene.render.bake.normal_g = 'POS_Y' - - # Set samples - bpy.context.scene.cycles.samples = samples - - # Speed up samples for simple render modes - if modes[mode].type == 'EMIT' or modes[mode].type == 'DIFFUSE': - bpy.context.scene.cycles.samples = 1 - - # Pixel Padding - bpy.context.scene.render.bake.margin = padding * sampling_scale - - # Disable Direct and Indirect for all 'DIFFUSE' bake types - if modes[mode].type == 'DIFFUSE': - bpy.context.scene.render.bake.use_pass_direct = False - bpy.context.scene.render.bake.use_pass_indirect = False - bpy.context.scene.render.bake.use_pass_color = True - - if obj_cage is None: - # Bake with Cage - bpy.ops.object.bake( - type=modes[mode].type, - use_clear=False, - cage_extrusion=ray_distance, - - use_selected_to_active=is_multi, - normal_space=modes[mode].normal_space - ) - else: - # Bake without Cage - bpy.ops.object.bake( - type=modes[mode].type, - use_clear=False, - cage_extrusion=ray_distance, - - use_selected_to_active=is_multi, - normal_space=modes[mode].normal_space, - - #Use Cage and assign object - use_cage=True, - cage_object=obj_cage.name - ) + + + # if modes[mode].engine == 'BLENDER_EEVEE': + # # Snippet: https://gist.github.com/AndrewRayCode/760c4634a77551827de41ed67585064b + # bpy.context.scene.render.bake_margin = padding + + # # AO Settings + # bpy.context.scene.render.bake_type = modes[mode].type + # bpy.context.scene.render.use_bake_normalize = True + + # if modes[mode].type == 'AO': + # bpy.context.scene.world.light_settings.use_ambient_occlusion = True + # bpy.context.scene.world.light_settings.gather_method = 'RAYTRACE' + # bpy.context.scene.world.light_settings.samples = samples + + # bpy.context.scene.render.use_bake_selected_to_active = is_multi + # bpy.context.scene.render.bake_distance = ray_distance + # bpy.context.scene.render.use_bake_clear = False + + # bpy.ops.object.bake_image() + + + if modes[mode].engine == 'CYCLES' or modes[mode].engine == 'BLENDER_EEVEE' : + + if modes[mode].normal_space == 'OBJECT': + #See: https://twitter.com/Linko_3D/status/963066705584054272 + bpy.context.scene.render.bake.normal_r = 'POS_X' + bpy.context.scene.render.bake.normal_g = 'POS_Z' + bpy.context.scene.render.bake.normal_b = 'NEG_Y' + + elif modes[mode].normal_space == 'TANGENT': + bpy.context.scene.render.bake.normal_r = 'POS_X' + bpy.context.scene.render.bake.normal_b = 'POS_Z' + # Adjust Y swizzle from Addon preferences + swizzle_y = bpy.context.preferences.addons["textools"].preferences.swizzle_y_coordinate + if swizzle_y == 'Y-': + bpy.context.scene.render.bake.normal_g = 'NEG_Y' + elif swizzle_y == 'Y+': + bpy.context.scene.render.bake.normal_g = 'POS_Y' + + # Set samples + bpy.context.scene.cycles.samples = samples + + # Speed up samples for simple render modes + if modes[mode].type == 'EMIT' or modes[mode].type == 'DIFFUSE': + bpy.context.scene.cycles.samples = 1 + + # Pixel Padding + bpy.context.scene.render.bake.margin = padding * sampling_scale + + # Disable Direct and Indirect for all 'DIFFUSE' bake types + if modes[mode].type == 'DIFFUSE': + bpy.context.scene.render.bake.use_pass_direct = False + bpy.context.scene.render.bake.use_pass_indirect = False + bpy.context.scene.render.bake.use_pass_color = True + + if obj_cage is None: + # Bake with Cage + bpy.ops.object.bake( + type=modes[mode].type, + use_clear=False, + cage_extrusion=ray_distance, + + use_selected_to_active=is_multi, + normal_space=modes[mode].normal_space + ) + else: + # Bake without Cage + bpy.ops.object.bake( + type=modes[mode].type, + use_clear=False, + cage_extrusion=ray_distance, + + use_selected_to_active=is_multi, + normal_space=modes[mode].normal_space, + + #Use Cage and assign object + use_cage=True, + cage_object=obj_cage.name + ) bpy.utils.register_class(op) diff --git a/op_bake_explode.py b/op_bake_explode.py index 77f442a..0a99679 100644 --- a/op_bake_explode.py +++ b/op_bake_explode.py @@ -11,211 +11,211 @@ frame_range = 50 class op(bpy.types.Operator): - bl_idname = "uv.textools_bake_explode" - bl_label = "Explode" - bl_description = "Explode selected bake pairs with animation keyframes" - bl_options = {'REGISTER', 'UNDO'} + bl_idname = "uv.textools_bake_explode" + bl_label = "Explode" + bl_description = "Explode selected bake pairs with animation keyframes" + bl_options = {'REGISTER', 'UNDO'} - @classmethod - def poll(cls, context): - if len(settings.sets) <= 1: - return False + @classmethod + def poll(cls, context): + if len(settings.sets) <= 1: + return False - return True + return True - def execute(self, context): - explode(self) + def execute(self, context): + explode(self) - return {'FINISHED'} + return {'FINISHED'} def explode(self): - sets = settings.sets + sets = settings.sets - set_bounds = {} - set_volume = {} - avg_side = 0 - for set in sets: - set_bounds[set] = get_bbox_set(set) - set_volume[set] = set_bounds[set]['size'].x * set_bounds[set]['size'].y * set_bounds[set]['size'].z + set_bounds = {} + set_volume = {} + avg_side = 0 + for set in sets: + set_bounds[set] = get_bbox_set(set) + set_volume[set] = set_bounds[set]['size'].x * set_bounds[set]['size'].y * set_bounds[set]['size'].z - avg_side+=set_bounds[set]['size'].x - avg_side+=set_bounds[set]['size'].y - avg_side+=set_bounds[set]['size'].z + avg_side+=set_bounds[set]['size'].x + avg_side+=set_bounds[set]['size'].y + avg_side+=set_bounds[set]['size'].z - avg_side/=(len(sets)*3) + avg_side/=(len(sets)*3) - sorted_set_volume = sorted(set_volume.items(), key=operator.itemgetter(1)) - sorted_sets = [item[0] for item in sorted_set_volume] - sorted_sets.reverse() + sorted_set_volume = sorted(set_volume.items(), key=operator.itemgetter(1)) + sorted_sets = [item[0] for item in sorted_set_volume] + sorted_sets.reverse() - # All combined bounding boxes - bbox_all = merge_bounds(list(set_bounds.values())) - bbox_max = set_bounds[ sorted_sets[0] ] # max_bbox(list(set_bounds.values())) + # All combined bounding boxes + bbox_all = merge_bounds(list(set_bounds.values())) + bbox_max = set_bounds[ sorted_sets[0] ] # max_bbox(list(set_bounds.values())) - # Offset sets into their direction - dir_offset_last_bbox = {} - for i in range(0,6): - dir_offset_last_bbox[i] = bbox_max #bbox_all + # Offset sets into their direction + dir_offset_last_bbox = {} + for i in range(0,6): + dir_offset_last_bbox[i] = bbox_max #bbox_all - bpy.context.scene.frame_start = 0 - bpy.context.scene.frame_end = frame_range - bpy.context.scene.frame_current = 0 + bpy.context.scene.frame_start = 0 + bpy.context.scene.frame_end = frame_range + bpy.context.scene.frame_current = 0 - - # Process each set - for set in sorted_sets: - if set_bounds[set] != bbox_max: - delta = set_bounds[set]['center'] - bbox_all['center'] - offset_set(set, delta, avg_side*0.35, dir_offset_last_bbox ) + + # Process each set + for set in sorted_sets: + if set_bounds[set] != bbox_max: + delta = set_bounds[set]['center'] - bbox_all['center'] + offset_set(set, delta, avg_side*0.35, dir_offset_last_bbox ) def offset_set(set, delta, margin, dir_offset_last_bbox): - objects = set.objects_low + set.objects_high + set.objects_cage - # print("\nSet '{}' with {}x".format(set.name, len(objects) )) - - # Which Direction? - delta_max = max(abs(delta.x), abs(delta.y), abs(delta.z)) - direction = [0,0,0] - if delta_max > 0: - for i in range(0,3): - if abs(delta[i]) == delta_max: - direction[i] = delta[i]/abs(delta[i]) - else: - direction[i] = 0 - else: - # Default when not delta offset was measure move up - direction = [0,0,1] - - delta = Vector((direction[0], direction[1], direction[2])) - - # Get Key - key = get_delta_key(delta) - - # Calculate Offset - bbox = get_bbox_set(set) - bbox_last = dir_offset_last_bbox[key] - - offset = Vector((0,0,0)) - - if delta.x == 1: - offset = delta * ( bbox_last['max'].x - bbox['min'].x ) - elif delta.x == -1: - offset = delta * -( bbox_last['min'].x - bbox['max'].x ) - - elif delta.y == 1: - offset = delta * ( bbox_last['max'].y - bbox['min'].y ) - elif delta.y == -1: - offset = delta * -( bbox_last['min'].y - bbox['max'].y ) - - elif delta.z == 1: - offset = delta * ( bbox_last['max'].z - bbox['min'].z ) - elif delta.z == -1: - offset = delta * -( bbox_last['min'].z - bbox['max'].z ) - - # Add margin - offset+= delta * margin - - # Offset items - # https://blenderartists.org/forum/showthread.php?237761-Blender-2-6-Set-keyframes-using-Python-script - # http://blenderscripting.blogspot.com.au/2011/05/inspired-by-post-on-ba-it-just-so.html - - # Set key A - bpy.context.scene.frame_current = 0 - for obj in objects: - obj.keyframe_insert(data_path="location") - - for obj in objects: - obj.location += offset - bpy.context.view_layer.update() - - # Set key B - bpy.context.scene.frame_current = frame_range - for obj in objects: - obj.keyframe_insert(data_path="location") - - # Update last bbox in direction - dir_offset_last_bbox[key] = get_bbox_set(set) + objects = set.objects_low + set.objects_high + set.objects_cage + # print("\nSet '{}' with {}x".format(set.name, len(objects) )) + + # Which Direction? + delta_max = max(abs(delta.x), abs(delta.y), abs(delta.z)) + direction = [0,0,0] + if delta_max > 0: + for i in range(0,3): + if abs(delta[i]) == delta_max: + direction[i] = delta[i]/abs(delta[i]) + else: + direction[i] = 0 + else: + # Default when not delta offset was measure move up + direction = [0,0,1] + + delta = Vector((direction[0], direction[1], direction[2])) + + # Get Key + key = get_delta_key(delta) + + # Calculate Offset + bbox = get_bbox_set(set) + bbox_last = dir_offset_last_bbox[key] + + offset = Vector((0,0,0)) + + if delta.x == 1: + offset = delta * ( bbox_last['max'].x - bbox['min'].x ) + elif delta.x == -1: + offset = delta * -( bbox_last['min'].x - bbox['max'].x ) + + elif delta.y == 1: + offset = delta * ( bbox_last['max'].y - bbox['min'].y ) + elif delta.y == -1: + offset = delta * -( bbox_last['min'].y - bbox['max'].y ) + + elif delta.z == 1: + offset = delta * ( bbox_last['max'].z - bbox['min'].z ) + elif delta.z == -1: + offset = delta * -( bbox_last['min'].z - bbox['max'].z ) + + # Add margin + offset+= delta * margin + + # Offset items + # https://blenderartists.org/forum/showthread.php?237761-Blender-2-6-Set-keyframes-using-Python-script + # http://blenderscripting.blogspot.com.au/2011/05/inspired-by-post-on-ba-it-just-so.html + + # Set key A + bpy.context.scene.frame_current = 0 + for obj in objects: + obj.keyframe_insert(data_path="location") + + for obj in objects: + obj.location += offset + bpy.context.view_layer.update() + + # Set key B + bpy.context.scene.frame_current = frame_range + for obj in objects: + obj.keyframe_insert(data_path="location") + + # Update last bbox in direction + dir_offset_last_bbox[key] = get_bbox_set(set) def get_delta_key(delta): - # print("Get key {} is: {}".format(delta, delta.y == -1 )) - if delta.x == -1: - return 0 - elif delta.x == 1: - return 1 - if delta.y == -1: - return 2 - elif delta.y == 1: - return 3 - if delta.z == -1: - return 4 - elif delta.z == 1: - return 5 + # print("Get key {} is: {}".format(delta, delta.y == -1 )) + if delta.x == -1: + return 0 + elif delta.x == 1: + return 1 + if delta.y == -1: + return 2 + elif delta.y == 1: + return 3 + if delta.z == -1: + return 4 + elif delta.z == 1: + return 5 def merge_bounds(bounds): - box_min = bounds[0]['min'].copy() - box_max = bounds[0]['max'].copy() - - for bbox in bounds: - # box_min.x = -8 - box_min.x = min(box_min.x, bbox['min'].x) - box_min.y = min(box_min.y, bbox['min'].y) - box_min.z = min(box_min.z, bbox['min'].z) - - box_max.x = max(box_max.x, bbox['max'].x) - box_max.y = max(box_max.y, bbox['max'].y) - box_max.z = max(box_max.z, bbox['max'].z) - - return { - 'min':box_min, - 'max':box_max, - 'size':(box_max-box_min), - 'center':box_min+(box_max-box_min)/2 - } + box_min = bounds[0]['min'].copy() + box_max = bounds[0]['max'].copy() + + for bbox in bounds: + # box_min.x = -8 + box_min.x = min(box_min.x, bbox['min'].x) + box_min.y = min(box_min.y, bbox['min'].y) + box_min.z = min(box_min.z, bbox['min'].z) + + box_max.x = max(box_max.x, bbox['max'].x) + box_max.y = max(box_max.y, bbox['max'].y) + box_max.z = max(box_max.z, bbox['max'].z) + + return { + 'min':box_min, + 'max':box_max, + 'size':(box_max-box_min), + 'center':box_min+(box_max-box_min)/2 + } def get_bbox_set(set): - objects = set.objects_low + set.objects_high + set.objects_cage - bounds = [] - for obj in objects: - bounds.append( get_bbox(obj) ) - return merge_bounds(bounds) + objects = set.objects_low + set.objects_high + set.objects_cage + bounds = [] + for obj in objects: + bounds.append( get_bbox(obj) ) + return merge_bounds(bounds) def get_bbox(obj): - corners = [obj.matrix_world @ Vector(corner) for corner in obj.bound_box] - - # Get world space Min / Max - box_min = Vector((corners[0].x, corners[0].y, corners[0].z)) - box_max = Vector((corners[0].x, corners[0].y, corners[0].z)) - for corner in corners: - # box_min.x = -8 - box_min.x = min(box_min.x, corner.x) - box_min.y = min(box_min.y, corner.y) - box_min.z = min(box_min.z, corner.z) - - box_max.x = max(box_max.x, corner.x) - box_max.y = max(box_max.y, corner.y) - box_max.z = max(box_max.z, corner.z) - - return { - 'min':box_min, - 'max':box_max, - 'size':(box_max-box_min), - 'center':box_min+(box_max-box_min)/2 - } + corners = [obj.matrix_world @ Vector(corner) for corner in obj.bound_box] + + # Get world space Min / Max + box_min = Vector((corners[0].x, corners[0].y, corners[0].z)) + box_max = Vector((corners[0].x, corners[0].y, corners[0].z)) + for corner in corners: + # box_min.x = -8 + box_min.x = min(box_min.x, corner.x) + box_min.y = min(box_min.y, corner.y) + box_min.z = min(box_min.z, corner.z) + + box_max.x = max(box_max.x, corner.x) + box_max.y = max(box_max.y, corner.y) + box_max.z = max(box_max.z, corner.z) + + return { + 'min':box_min, + 'max':box_max, + 'size':(box_max-box_min), + 'center':box_min+(box_max-box_min)/2 + } bpy.utils.register_class(op) \ No newline at end of file diff --git a/op_bake_organize_names.py b/op_bake_organize_names.py index 0faf688..0b5719c 100644 --- a/op_bake_organize_names.py +++ b/op_bake_organize_names.py @@ -10,170 +10,170 @@ from . import utilities_bake class op(bpy.types.Operator): - bl_idname = "uv.textools_bake_organize_names" - bl_label = "Match Names" - bl_description = "Match high poly object names to low poly objects by their bounding boxes." - bl_options = {'REGISTER', 'UNDO'} + bl_idname = "uv.textools_bake_organize_names" + bl_label = "Match Names" + bl_description = "Match high poly object names to low poly objects by their bounding boxes." + bl_options = {'REGISTER', 'UNDO'} - @classmethod - def poll(cls, context): - # Require 2 or more objects to sort - if len(bpy.context.selected_objects) <= 1: - return False + @classmethod + def poll(cls, context): + # Require 2 or more objects to sort + if len(bpy.context.selected_objects) <= 1: + return False - return True + return True - def execute(self, context): - sort_objects(self) - return {'FINISHED'} + def execute(self, context): + sort_objects(self) + return {'FINISHED'} def sort_objects(self): - # Collect objects - objects = [] - bounds = {} - for obj in bpy.context.selected_objects: - if obj.type == 'MESH': - objects.append(obj) - bounds[obj] = get_bbox(obj) - - print("Objects {}x".format(len(objects))) - - # Get smallest side of any bounding box - min_side = min(bounds[objects[0]]['size'].x, bounds[objects[0]]['size'].y, bounds[objects[0]]['size'].z) - avg_side = 0 - for obj in bounds: - min_side = min(min_side, bounds[obj]['size'].x, bounds[obj]['size'].y, bounds[obj]['size'].z) - avg_side+=bounds[obj]['size'].x - avg_side+=bounds[obj]['size'].y - avg_side+=bounds[obj]['size'].z - avg_side/=(len(bounds)*3) - - # Get all Low and high poly objects - objects_low = [obj for obj in objects if utilities_bake.get_object_type(obj)=='low'] - objects_high = [obj for obj in objects if utilities_bake.get_object_type(obj)=='high'] - - if len(objects_low) == 0: - self.report({'ERROR_INVALID_INPUT'}, "There are no low poly objects selected") - return - elif len(objects_high) == 0: - self.report({'ERROR_INVALID_INPUT'}, "There are no high poly objects selected") - return - - print("Low {}x, High {}x".format(len(objects_low),len(objects_high))) - - pairs_low_high = {} - - objects_left_high = objects_high.copy() - for obj_A in objects_low: - - matches = {} - for obj_B in objects_left_high: - score = get_score(obj_A, obj_B) - p = score / avg_side - if p > 0 and p <= 0.65: - matches[obj_B] = p - else: - print("Not matched: {} ".format(p)) - - if(len(matches) > 0): - sorted_matches = sorted(matches.items(), key=operator.itemgetter(1)) - for i in range(0, len(sorted_matches)): - A = obj_A - B = sorted_matches[i][0] - p = sorted_matches[i][1] - print("Check: {}% '{}' = '{}' ".format(int(p * 100.0), A.name, B.name )) - - # Remove from list - objects_left_high.remove(sorted_matches[0][0]) - pairs_low_high[obj_A] = sorted_matches[0][0] - print("") - - # objects_unsorted = [obj for obj in objects if (obj not in pairs_low_high.values() and obj not in pairs_low_high.keys() )] - - bpy.ops.object.select_all(action='DESELECT') - for obj_A in pairs_low_high: - obj_B = pairs_low_high[obj_A] - try: - obj_B.name = utilities_bake.get_bake_name(obj_A)+" high" - - obj_A.select_set( state = True, view_layer = None) - obj_B.select_set( state = True, view_layer = None) - except: - print("Fail") - - print("Matched {}x".format(len(pairs_low_high))) + # Collect objects + objects = [] + bounds = {} + for obj in bpy.context.selected_objects: + if obj.type == 'MESH': + objects.append(obj) + bounds[obj] = get_bbox(obj) + + print("Objects {}x".format(len(objects))) + + # Get smallest side of any bounding box + min_side = min(bounds[objects[0]]['size'].x, bounds[objects[0]]['size'].y, bounds[objects[0]]['size'].z) + avg_side = 0 + for obj in bounds: + min_side = min(min_side, bounds[obj]['size'].x, bounds[obj]['size'].y, bounds[obj]['size'].z) + avg_side+=bounds[obj]['size'].x + avg_side+=bounds[obj]['size'].y + avg_side+=bounds[obj]['size'].z + avg_side/=(len(bounds)*3) + + # Get all Low and high poly objects + objects_low = [obj for obj in objects if utilities_bake.get_object_type(obj)=='low'] + objects_high = [obj for obj in objects if utilities_bake.get_object_type(obj)=='high'] + + if len(objects_low) == 0: + self.report({'ERROR_INVALID_INPUT'}, "There are no low poly objects selected") + return + elif len(objects_high) == 0: + self.report({'ERROR_INVALID_INPUT'}, "There are no high poly objects selected") + return + + print("Low {}x, High {}x".format(len(objects_low),len(objects_high))) + + pairs_low_high = {} + + objects_left_high = objects_high.copy() + for obj_A in objects_low: + + matches = {} + for obj_B in objects_left_high: + score = get_score(obj_A, obj_B) + p = score / avg_side + if p > 0 and p <= 0.65: + matches[obj_B] = p + else: + print("Not matched: {} ".format(p)) + + if(len(matches) > 0): + sorted_matches = sorted(matches.items(), key=operator.itemgetter(1)) + for i in range(0, len(sorted_matches)): + A = obj_A + B = sorted_matches[i][0] + p = sorted_matches[i][1] + print("Check: {}% '{}' = '{}' ".format(int(p * 100.0), A.name, B.name )) + + # Remove from list + objects_left_high.remove(sorted_matches[0][0]) + pairs_low_high[obj_A] = sorted_matches[0][0] + print("") + + # objects_unsorted = [obj for obj in objects if (obj not in pairs_low_high.values() and obj not in pairs_low_high.keys() )] + + bpy.ops.object.select_all(action='DESELECT') + for obj_A in pairs_low_high: + obj_B = pairs_low_high[obj_A] + try: + obj_B.name = utilities_bake.get_bake_name(obj_A)+" high" + + obj_A.select_set( state = True, view_layer = None) + obj_B.select_set( state = True, view_layer = None) + except: + print("Fail") + + print("Matched {}x".format(len(pairs_low_high))) def get_score(A, B): - bbox_A = get_bbox(A) - bbox_B = get_bbox(B) + bbox_A = get_bbox(A) + bbox_B = get_bbox(B) - # Not colliding - if not is_colliding(bbox_A, bbox_B): - return -1.0 + # Not colliding + if not is_colliding(bbox_A, bbox_B): + return -1.0 - # Position - delta_pos = (bbox_B['center'] - bbox_A['center']).length + # Position + delta_pos = (bbox_B['center'] - bbox_A['center']).length - # Volume - volume_A = bbox_A['size'].x * bbox_A['size'].y * bbox_A['size'].z - volume_B = bbox_B['size'].x * bbox_B['size'].y * bbox_B['size'].z - delta_vol = (max(volume_A, volume_B) - min(volume_A, volume_B))/3.0 + # Volume + volume_A = bbox_A['size'].x * bbox_A['size'].y * bbox_A['size'].z + volume_B = bbox_B['size'].x * bbox_B['size'].y * bbox_B['size'].z + delta_vol = (max(volume_A, volume_B) - min(volume_A, volume_B))/3.0 - # Longest side - side_A_max = max(bbox_A['size'].x, bbox_A['size'].y, bbox_A['size'].z ) - side_B_max = max(bbox_B['size'].x, bbox_B['size'].y, bbox_B['size'].z ) - delta_size_max = abs(side_A_max - side_B_max) + # Longest side + side_A_max = max(bbox_A['size'].x, bbox_A['size'].y, bbox_A['size'].z ) + side_B_max = max(bbox_B['size'].x, bbox_B['size'].y, bbox_B['size'].z ) + delta_size_max = abs(side_A_max - side_B_max) - return delta_pos + delta_vol + delta_size_max + return delta_pos + delta_vol + delta_size_max def get_bbox(obj): - corners = [obj.matrix_world @ Vector(corner) for corner in obj.bound_box] - - # Get world space Min / Max - box_min = Vector((corners[0].x, corners[0].y, corners[0].z)) - box_max = Vector((corners[0].x, corners[0].y, corners[0].z)) - for corner in corners: - # box_min.x = -8 - box_min.x = min(box_min.x, corner.x) - box_min.y = min(box_min.y, corner.y) - box_min.z = min(box_min.z, corner.z) - - box_max.x = max(box_max.x, corner.x) - box_max.y = max(box_max.y, corner.y) - box_max.z = max(box_max.z, corner.z) - - return { - 'min':box_min, - 'max':box_max, - 'size':(box_max-box_min), - 'center':box_min+(box_max-box_min)/2 - } + corners = [obj.matrix_world @ Vector(corner) for corner in obj.bound_box] + + # Get world space Min / Max + box_min = Vector((corners[0].x, corners[0].y, corners[0].z)) + box_max = Vector((corners[0].x, corners[0].y, corners[0].z)) + for corner in corners: + # box_min.x = -8 + box_min.x = min(box_min.x, corner.x) + box_min.y = min(box_min.y, corner.y) + box_min.z = min(box_min.z, corner.z) + + box_max.x = max(box_max.x, corner.x) + box_max.y = max(box_max.y, corner.y) + box_max.z = max(box_max.z, corner.z) + + return { + 'min':box_min, + 'max':box_max, + 'size':(box_max-box_min), + 'center':box_min+(box_max-box_min)/2 + } def is_colliding(bbox_A, bbox_B): - def is_collide_1D(A_min, A_max, B_min, B_max): - # One line is inside the other - length_A = A_max-A_min - length_B = B_max-B_min - center_A = A_min + length_A/2 - center_B = B_min + length_B/2 + def is_collide_1D(A_min, A_max, B_min, B_max): + # One line is inside the other + length_A = A_max-A_min + length_B = B_max-B_min + center_A = A_min + length_A/2 + center_B = B_min + length_B/2 - return abs(center_A - center_B) <= (length_A+length_B)/2 + return abs(center_A - center_B) <= (length_A+length_B)/2 - collide_x = is_collide_1D(bbox_A['min'].x, bbox_A['max'].x, bbox_B['min'].x, bbox_B['max'].x) - collide_y = is_collide_1D(bbox_A['min'].y, bbox_A['max'].y, bbox_B['min'].y, bbox_B['max'].y) - collide_z = is_collide_1D(bbox_A['min'].z, bbox_A['max'].z, bbox_B['min'].z, bbox_B['max'].z) + collide_x = is_collide_1D(bbox_A['min'].x, bbox_A['max'].x, bbox_B['min'].x, bbox_B['max'].x) + collide_y = is_collide_1D(bbox_A['min'].y, bbox_A['max'].y, bbox_B['min'].y, bbox_B['max'].y) + collide_z = is_collide_1D(bbox_A['min'].z, bbox_A['max'].z, bbox_B['min'].z, bbox_B['max'].z) - return collide_x and collide_y and collide_z + return collide_x and collide_y and collide_z bpy.utils.register_class(op) diff --git a/op_color_assign.py b/op_color_assign.py index 55635b1..08c9a55 100644 --- a/op_color_assign.py +++ b/op_color_assign.py @@ -8,91 +8,91 @@ from math import pi from . import utilities_color class op(bpy.types.Operator): - bl_idname = "uv.textools_color_assign" - bl_label = "Assign Color" - bl_description = "Assign color to selected objects or faces in edit mode." - bl_options = {'REGISTER', 'UNDO'} - - index : bpy.props.IntProperty(description="Color Index", default=0) + bl_idname = "uv.textools_color_assign" + bl_label = "Assign Color" + bl_description = "Assign color to selected objects or faces in edit mode." + bl_options = {'REGISTER', 'UNDO'} + + index : bpy.props.IntProperty(description="Color Index", default=0) - @classmethod - def poll(cls, context): - if not bpy.context.active_object: - return False + @classmethod + def poll(cls, context): + if not bpy.context.active_object: + return False - if bpy.context.active_object not in bpy.context.selected_objects: - return False + if bpy.context.active_object not in bpy.context.selected_objects: + return False - if bpy.context.active_object.type != 'MESH': - return False + if bpy.context.active_object.type != 'MESH': + return False - #Only in UV editor mode - if bpy.context.area.type != 'IMAGE_EDITOR': - return False + #Only in UV editor mode + if bpy.context.area.type != 'IMAGE_EDITOR': + return False - return True - - def execute(self, context): - assign_color(self, context, self.index) - return {'FINISHED'} + return True + + def execute(self, context): + assign_color(self, context, self.index) + return {'FINISHED'} def assign_color(self, context, index): - - selected_obj = bpy.context.selected_objects.copy() - - previous_mode = 'OBJECT' - if len(selected_obj) == 1: - previous_mode = bpy.context.active_object.mode - - - for obj in selected_obj: - # Select object - bpy.ops.object.mode_set(mode='OBJECT') - bpy.ops.object.select_all(action='DESELECT') - obj.select_set( state = True, view_layer = None) - bpy.context.view_layer.objects.active = obj - - # Enter Edit mode - bpy.ops.object.mode_set(mode='EDIT') - bm = bmesh.from_edit_mesh(obj.data); - faces = [] - - #Assign to all or just selected faces? - if previous_mode == 'EDIT': - faces = [face for face in bm.faces if face.select] - else: - faces = [face for face in bm.faces] - - if previous_mode == 'OBJECT': - bpy.ops.mesh.select_all(action='SELECT') - - - # Verify material slots - for i in range(index+1): - if index >= len(obj.material_slots): - bpy.ops.object.material_slot_add() - - utilities_color.assign_slot(obj, index) - - # Assign to selection - obj.active_material_index = index - bpy.ops.object.material_slot_assign() - - - #Change View mode to MATERIAL - # for area in bpy.context.screen.areas: - # if area.type == 'VIEW_3D': - # for space in area.spaces: - # if space.type == 'VIEW_3D': - # space.shading.type = 'MATERIAL' - - # restore mode - bpy.ops.object.mode_set(mode='OBJECT') - bpy.ops.object.select_all(action='DESELECT') - for obj in selected_obj: - obj.select_set( state = True, view_layer = None) - bpy.ops.object.mode_set(mode=previous_mode) - -bpy.utils.register_class(op) \ No newline at end of file + + selected_obj = bpy.context.selected_objects.copy() + + previous_mode = 'OBJECT' + if len(selected_obj) == 1: + previous_mode = bpy.context.active_object.mode + + + for obj in selected_obj: + # Select object + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.select_all(action='DESELECT') + obj.select_set( state = True, view_layer = None) + bpy.context.view_layer.objects.active = obj + + # Enter Edit mode + bpy.ops.object.mode_set(mode='EDIT') + bm = bmesh.from_edit_mesh(obj.data); + faces = [] + + #Assign to all or just selected faces? + if previous_mode == 'EDIT': + faces = [face for face in bm.faces if face.select] + else: + faces = [face for face in bm.faces] + + if previous_mode == 'OBJECT': + bpy.ops.mesh.select_all(action='SELECT') + + + # Verify material slots + for i in range(index+1): + if index >= len(obj.material_slots): + bpy.ops.object.material_slot_add() + + utilities_color.assign_slot(obj, index) + + # Assign to selection + obj.active_material_index = index + bpy.ops.object.material_slot_assign() + + + #Change View mode to MATERIAL + # for area in bpy.context.screen.areas: + # if area.type == 'VIEW_3D': + # for space in area.spaces: + # if space.type == 'VIEW_3D': + # space.shading.type = 'MATERIAL' + + # restore mode + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.select_all(action='DESELECT') + for obj in selected_obj: + obj.select_set( state = True, view_layer = None) + bpy.ops.object.mode_set(mode=previous_mode) + +bpy.utils.register_class(op) \ No newline at end of file diff --git a/op_color_clear.py b/op_color_clear.py index dca9d6a..f6028e9 100644 --- a/op_color_clear.py +++ b/op_color_clear.py @@ -8,89 +8,89 @@ from math import pi from . import utilities_color class op(bpy.types.Operator): - bl_idname = "uv.textools_color_clear" - bl_label = "Clear Colors" - bl_description = "Clears the Color IDs and materials on the selected model" - bl_options = {'REGISTER', 'UNDO'} - + bl_idname = "uv.textools_color_clear" + bl_label = "Clear Colors" + bl_description = "Clears the Color IDs and materials on the selected model" + bl_options = {'REGISTER', 'UNDO'} + - @classmethod - def poll(cls, context): - if not bpy.context.active_object: - return False + @classmethod + def poll(cls, context): + if not bpy.context.active_object: + return False - if bpy.context.active_object not in bpy.context.selected_objects: - return False + if bpy.context.active_object not in bpy.context.selected_objects: + return False - if bpy.context.active_object.type != 'MESH': - return False + if bpy.context.active_object.type != 'MESH': + return False - #Only in UV editor mode - if bpy.context.area.type != 'IMAGE_EDITOR': - return False + #Only in UV editor mode + if bpy.context.area.type != 'IMAGE_EDITOR': + return False - return True - - def execute(self, context): - clear_colors(self, context) - return {'FINISHED'} + return True + + def execute(self, context): + clear_colors(self, context) + return {'FINISHED'} def clear_colors(self, context): - obj = bpy.context.active_object - - - - # Store previous mode - previous_mode = bpy.context.active_object.mode - if bpy.context.active_object.mode != 'EDIT': - bpy.ops.object.mode_set(mode='EDIT') - - bm = bmesh.from_edit_mesh(bpy.context.active_object.data); - - # Set all faces - for face in bm.faces: - face.material_index = 0 - - # Clear material slots - bpy.ops.object.mode_set(mode='OBJECT') - count = len(obj.material_slots) - for i in range(count): - bpy.ops.object.material_slot_remove() - - # Delete materials if not used - for material in bpy.data.materials: - if utilities_color.material_prefix in material.name: - if material.users == 0: - material.user_clear() - bpy.data.materials.remove(material) - - # Restore previous mode - bpy.ops.object.mode_set(mode=previous_mode) - - - for area in bpy.context.screen.areas: - print("area: {}".format(area.type)) - if area.type == 'PROPERTIES': - for space in area.spaces: - if space.type == 'PROPERTIES': - # space.shading.type = 'MATERIAL' - space.context = 'MATERIAL' - - # Show Material Tab - for area in bpy.context.screen.areas: - if area.type == 'PROPERTIES': - for space in area.spaces: - if space.type == 'PROPERTIES': - space.context = 'MATERIAL' - - # Switch Solid shading - for area in bpy.context.screen.areas: - if area.type == 'VIEW_3D': - for space in area.spaces: - if space.type == 'VIEW_3D': - space.shading.type = 'SOLID' + obj = bpy.context.active_object + + + + # Store previous mode + previous_mode = bpy.context.active_object.mode + if bpy.context.active_object.mode != 'EDIT': + bpy.ops.object.mode_set(mode='EDIT') + + bm = bmesh.from_edit_mesh(bpy.context.active_object.data); + + # Set all faces + for face in bm.faces: + face.material_index = 0 + + # Clear material slots + bpy.ops.object.mode_set(mode='OBJECT') + count = len(obj.material_slots) + for i in range(count): + bpy.ops.object.material_slot_remove() + + # Delete materials if not used + for material in bpy.data.materials: + if utilities_color.material_prefix in material.name: + if material.users == 0: + material.user_clear() + bpy.data.materials.remove(material) + + # Restore previous mode + bpy.ops.object.mode_set(mode=previous_mode) + + + for area in bpy.context.screen.areas: + print("area: {}".format(area.type)) + if area.type == 'PROPERTIES': + for space in area.spaces: + if space.type == 'PROPERTIES': + # space.shading.type = 'MATERIAL' + space.context = 'MATERIAL' + + # Show Material Tab + for area in bpy.context.screen.areas: + if area.type == 'PROPERTIES': + for space in area.spaces: + if space.type == 'PROPERTIES': + space.context = 'MATERIAL' + + # Switch Solid shading + for area in bpy.context.screen.areas: + if area.type == 'VIEW_3D': + for space in area.spaces: + if space.type == 'VIEW_3D': + space.shading.type = 'SOLID' bpy.utils.register_class(op) \ No newline at end of file diff --git a/op_color_convert_texture.py b/op_color_convert_texture.py index aac12e9..d1f65b2 100644 --- a/op_color_convert_texture.py +++ b/op_color_convert_texture.py @@ -13,141 +13,141 @@ material_prefix = "TT_atlas_" gamma = 2.2 class op(bpy.types.Operator): - bl_idname = "uv.textools_color_convert_to_texture" - bl_label = "Pack Texture" - bl_description = "Pack ID Colors into single texture and UVs" - bl_options = {'REGISTER', 'UNDO'} - + bl_idname = "uv.textools_color_convert_to_texture" + bl_label = "Pack Texture" + bl_description = "Pack ID Colors into single texture and UVs" + bl_options = {'REGISTER', 'UNDO'} + - @classmethod - def poll(cls, context): - if not bpy.context.active_object: - return False + @classmethod + def poll(cls, context): + if not bpy.context.active_object: + return False - if bpy.context.active_object not in bpy.context.selected_objects: - return False + if bpy.context.active_object not in bpy.context.selected_objects: + return False - if len(bpy.context.selected_objects) != 1: - return False + if len(bpy.context.selected_objects) != 1: + return False - if bpy.context.active_object.type != 'MESH': - return False + if bpy.context.active_object.type != 'MESH': + return False - #Only in UV editor mode - if bpy.context.area.type != 'IMAGE_EDITOR': - return False + #Only in UV editor mode + if bpy.context.area.type != 'IMAGE_EDITOR': + return False - return True - - def execute(self, context): - pack_texture(self, context) - return {'FINISHED'} + return True + + def execute(self, context): + pack_texture(self, context) + return {'FINISHED'} def pack_texture(self, context): - obj = bpy.context.active_object - name = material_prefix+obj.name - - if obj.mode != 'OBJECT': - bpy.ops.object.mode_set(mode='OBJECT') - - - # Determine size - size_pixel = 8 - size_square = math.ceil(math.sqrt( context.scene.texToolsSettings.color_ID_count )) - size_image = size_square * size_pixel - size_image_pow = int(math.pow(2, math.ceil(math.log(size_image, 2)))) - - # Maximize pixel size - size_pixel = math.floor(size_image_pow/size_square) - - print("{0} colors = {1} x {1} = ({2}pix) {3} x {3} | {4} x {4}".format( - context.scene.texToolsSettings.color_ID_count, - size_square, - size_pixel, - size_image, - size_image_pow - )) - - # Create image - image = bpy.data.images.new(name, width=size_image_pow, height=size_image_pow) - pixels = [None] * size_image_pow * size_image_pow - - # Black pixels - for x in range(size_image_pow): - for y in range(size_image_pow): - pixels[(y * size_image_pow) + x] = [0, 0, 0, 1] - - # Pixels - for c in range(context.scene.texToolsSettings.color_ID_count): - x = c % size_square - y = math.floor(c/size_square) - color = utilities_color.get_color(c).copy() - for i in range(3): - color[i] = pow(color[i] , 1.0/gamma) - - for sx in range(size_pixel): - for sy in range(size_pixel): - _x = x*size_pixel + sx - _y = y*size_pixel + sy - pixels[(_y * size_image_pow) + _x] = [color[0], color[1], color[2], 1] - - - # flatten list & assign pixels - pixels = [chan for px in pixels for chan in px] - image.pixels = pixels - - # Set background image - for area in bpy.context.screen.areas: - if area.type == 'IMAGE_EDITOR': - area.spaces[0].image = image - - # Edit mesh - bpy.ops.object.mode_set(mode='EDIT') - bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE') - bpy.ops.mesh.select_all(action='SELECT') - # bpy.ops.uv.smart_project(angle_limit=1) - bpy.ops.uv.unwrap(method='ANGLE_BASED', margin=0.0078) - - - bm = bmesh.from_edit_mesh(bpy.context.active_object.data) - uv_layers = bm.loops.layers.uv.verify(); - - for face in bm.faces: - index = face.material_index - - # Get UV coordinates for index - x = index%size_square - y = math.floor(index/size_square) - - x*= (size_pixel / size_image_pow) - y*= (size_pixel / size_image_pow) - x+= size_pixel/size_image_pow/2 - y+= size_pixel/size_image_pow/2 - - for loop in face.loops: - loop[uv_layers].uv = (x, y) - - # Remove Slots & add one - bpy.ops.object.mode_set(mode='OBJECT') - bpy.ops.uv.textools_color_clear() - bpy.ops.object.material_slot_add() - - #Create material with image - obj.material_slots[0].material = utilities_bake.get_image_material(image) - - #Display UVs - bpy.ops.object.mode_set(mode='EDIT') - - # Switch textured shading - for area in bpy.context.screen.areas: - if area.type == 'VIEW_3D': - for space in area.spaces: - if space.type == 'VIEW_3D': - space.shading.type = 'MATERIAL' - - bpy.ops.ui.textools_popup('INVOKE_DEFAULT', message="Packed texture with {} color IDs".format( context.scene.texToolsSettings.color_ID_count )) + obj = bpy.context.active_object + name = material_prefix+obj.name + + if obj.mode != 'OBJECT': + bpy.ops.object.mode_set(mode='OBJECT') + + + # Determine size + size_pixel = 8 + size_square = math.ceil(math.sqrt( context.scene.texToolsSettings.color_ID_count )) + size_image = size_square * size_pixel + size_image_pow = int(math.pow(2, math.ceil(math.log(size_image, 2)))) + + # Maximize pixel size + size_pixel = math.floor(size_image_pow/size_square) + + print("{0} colors = {1} x {1} = ({2}pix) {3} x {3} | {4} x {4}".format( + context.scene.texToolsSettings.color_ID_count, + size_square, + size_pixel, + size_image, + size_image_pow + )) + + # Create image + image = bpy.data.images.new(name, width=size_image_pow, height=size_image_pow) + pixels = [None] * size_image_pow * size_image_pow + + # Black pixels + for x in range(size_image_pow): + for y in range(size_image_pow): + pixels[(y * size_image_pow) + x] = [0, 0, 0, 1] + + # Pixels + for c in range(context.scene.texToolsSettings.color_ID_count): + x = c % size_square + y = math.floor(c/size_square) + color = utilities_color.get_color(c).copy() + for i in range(3): + color[i] = pow(color[i] , 1.0/gamma) + + for sx in range(size_pixel): + for sy in range(size_pixel): + _x = x*size_pixel + sx + _y = y*size_pixel + sy + pixels[(_y * size_image_pow) + _x] = [color[0], color[1], color[2], 1] + + + # flatten list & assign pixels + pixels = [chan for px in pixels for chan in px] + image.pixels = pixels + + # Set background image + for area in bpy.context.screen.areas: + if area.type == 'IMAGE_EDITOR': + area.spaces[0].image = image + + # Edit mesh + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE') + bpy.ops.mesh.select_all(action='SELECT') + # bpy.ops.uv.smart_project(angle_limit=1) + bpy.ops.uv.unwrap(method='ANGLE_BASED', margin=0.0078) + + + bm = bmesh.from_edit_mesh(bpy.context.active_object.data) + uv_layers = bm.loops.layers.uv.verify(); + + for face in bm.faces: + index = face.material_index + + # Get UV coordinates for index + x = index%size_square + y = math.floor(index/size_square) + + x*= (size_pixel / size_image_pow) + y*= (size_pixel / size_image_pow) + x+= size_pixel/size_image_pow/2 + y+= size_pixel/size_image_pow/2 + + for loop in face.loops: + loop[uv_layers].uv = (x, y) + + # Remove Slots & add one + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.uv.textools_color_clear() + bpy.ops.object.material_slot_add() + + #Create material with image + obj.material_slots[0].material = utilities_bake.get_image_material(image) + + #Display UVs + bpy.ops.object.mode_set(mode='EDIT') + + # Switch textured shading + for area in bpy.context.screen.areas: + if area.type == 'VIEW_3D': + for space in area.spaces: + if space.type == 'VIEW_3D': + space.shading.type = 'MATERIAL' + + bpy.ops.ui.textools_popup('INVOKE_DEFAULT', message="Packed texture with {} color IDs".format( context.scene.texToolsSettings.color_ID_count )) bpy.utils.register_class(op) \ No newline at end of file diff --git a/op_color_convert_vertex_colors.py b/op_color_convert_vertex_colors.py index 9c4252c..47c72ed 100644 --- a/op_color_convert_vertex_colors.py +++ b/op_color_convert_vertex_colors.py @@ -12,79 +12,79 @@ from . import utilities_ui gamma = 2.2 class op(bpy.types.Operator): - bl_idname = "uv.textools_color_convert_to_vertex_colors" - bl_label = "Pack Texture" - bl_description = "Pack ID Colors into single texture and UVs" - bl_options = {'REGISTER', 'UNDO'} - + bl_idname = "uv.textools_color_convert_to_vertex_colors" + bl_label = "Pack Texture" + bl_description = "Pack ID Colors into single texture and UVs" + bl_options = {'REGISTER', 'UNDO'} + - @classmethod - def poll(cls, context): - if not bpy.context.active_object: - return False + @classmethod + def poll(cls, context): + if not bpy.context.active_object: + return False - if bpy.context.active_object not in bpy.context.selected_objects: - return False + if bpy.context.active_object not in bpy.context.selected_objects: + return False - if len(bpy.context.selected_objects) != 1: - return False + if len(bpy.context.selected_objects) != 1: + return False - if bpy.context.active_object.type != 'MESH': - return False + if bpy.context.active_object.type != 'MESH': + return False - #Only in UV editor mode - if bpy.context.area.type != 'IMAGE_EDITOR': - return False + #Only in UV editor mode + if bpy.context.area.type != 'IMAGE_EDITOR': + return False - return True - - def execute(self, context): - convert_vertex_colors(self, context) - return {'FINISHED'} + return True + + def execute(self, context): + convert_vertex_colors(self, context) + return {'FINISHED'} def convert_vertex_colors(self, context): - obj = bpy.context.active_object - - for i in range(len(obj.material_slots)): - slot = obj.material_slots[i] - if slot.material: - - # Select related faces - bpy.ops.object.mode_set(mode='EDIT') - bpy.ops.mesh.select_all(action='DESELECT') - - bm = bmesh.from_edit_mesh(bpy.context.active_object.data) - for face in bm.faces: - if face.material_index == i: - face.select = True - - color = utilities_color.get_color(i).copy() - # Fix Gamma - color[0] = pow(color[0],1/gamma) - color[1] = pow(color[1],1/gamma) - color[2] = pow(color[2],1/gamma) - - bpy.ops.object.mode_set(mode='VERTEX_PAINT') - bpy.context.tool_settings.vertex_paint.brush.color = color - bpy.context.object.data.use_paint_mask = True - bpy.ops.paint.vertex_color_set() - - # Back to object mode - bpy.ops.object.mode_set(mode='VERTEX_PAINT') - bpy.context.object.data.use_paint_mask = False - - # Switch textured shading - for area in bpy.context.screen.areas: - if area.type == 'VIEW_3D': - for space in area.spaces: - if space.type == 'VIEW_3D': - space.shading.type = 'MATERIAL' - - # Clear any materials - bpy.ops.uv.textools_color_clear() - - bpy.ops.ui.textools_popup('INVOKE_DEFAULT', message="Vertex colors assigned") + obj = bpy.context.active_object + + for i in range(len(obj.material_slots)): + slot = obj.material_slots[i] + if slot.material: + + # Select related faces + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.select_all(action='DESELECT') + + bm = bmesh.from_edit_mesh(bpy.context.active_object.data) + for face in bm.faces: + if face.material_index == i: + face.select = True + + color = utilities_color.get_color(i).copy() + # Fix Gamma + color[0] = pow(color[0],1/gamma) + color[1] = pow(color[1],1/gamma) + color[2] = pow(color[2],1/gamma) + + bpy.ops.object.mode_set(mode='VERTEX_PAINT') + bpy.context.tool_settings.vertex_paint.brush.color = color + bpy.context.object.data.use_paint_mask = True + bpy.ops.paint.vertex_color_set() + + # Back to object mode + bpy.ops.object.mode_set(mode='VERTEX_PAINT') + bpy.context.object.data.use_paint_mask = False + + # Switch textured shading + for area in bpy.context.screen.areas: + if area.type == 'VIEW_3D': + for space in area.spaces: + if space.type == 'VIEW_3D': + space.shading.type = 'MATERIAL' + + # Clear any materials + bpy.ops.uv.textools_color_clear() + + bpy.ops.ui.textools_popup('INVOKE_DEFAULT', message="Vertex colors assigned") bpy.utils.register_class(op) diff --git a/op_color_from_directions.py b/op_color_from_directions.py index 95b2c00..520cef1 100644 --- a/op_color_from_directions.py +++ b/op_color_from_directions.py @@ -8,199 +8,199 @@ from math import pi from . import utilities_color class op(bpy.types.Operator): - bl_idname = "uv.textools_color_from_directions" - bl_label = "Color Directions" - bl_description = "Assign a color ID to different face directions" - bl_options = {'REGISTER', 'UNDO'} - - directions : bpy.props.EnumProperty(items= - [('2', '2', 'Top & Bottom, Sides'), - ('3', '3', 'Top & Bottom, Left & Right, Front & Back'), - ('4', '4', 'Top, Left & Right, Front & Back, Bottom'), - ('6', '6', 'All sides')], - name = "Directions", - default = '3' - ) - def invoke(self, context, event): - wm = context.window_manager - return wm.invoke_props_dialog(self) - - - # def draw(self, context): - # layout = self.layout - # layout.prop(self, "directions") - - @classmethod - def poll(cls, context): - if not bpy.context.active_object: - return False - - if bpy.context.active_object not in bpy.context.selected_objects: - return False - - if len(bpy.context.selected_objects) != 1: - return False - - if bpy.context.active_object.type != 'MESH': - return False - - #Only in UV editor mode - if bpy.context.area.type != 'IMAGE_EDITOR': - return False - - return True - - def execute(self, context): - color_elements(self, context) - return {'FINISHED'} + bl_idname = "uv.textools_color_from_directions" + bl_label = "Color Directions" + bl_description = "Assign a color ID to different face directions" + bl_options = {'REGISTER', 'UNDO'} + + directions : bpy.props.EnumProperty(items= + [('2', '2', 'Top & Bottom, Sides'), + ('3', '3', 'Top & Bottom, Left & Right, Front & Back'), + ('4', '4', 'Top, Left & Right, Front & Back, Bottom'), + ('6', '6', 'All sides')], + name = "Directions", + default = '3' + ) + def invoke(self, context, event): + wm = context.window_manager + return wm.invoke_props_dialog(self) + + + # def draw(self, context): + # layout = self.layout + # layout.prop(self, "directions") + + @classmethod + def poll(cls, context): + if not bpy.context.active_object: + return False + + if bpy.context.active_object not in bpy.context.selected_objects: + return False + + if len(bpy.context.selected_objects) != 1: + return False + + if bpy.context.active_object.type != 'MESH': + return False + + #Only in UV editor mode + if bpy.context.area.type != 'IMAGE_EDITOR': + return False + + return True + + def execute(self, context): + color_elements(self, context) + return {'FINISHED'} def color_elements(self, context): - obj = bpy.context.active_object - - # Setup Edit & Face mode - if obj.mode != 'EDIT': - bpy.ops.object.mode_set(mode='EDIT') - bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE') - - # Collect groups - bm = bmesh.from_edit_mesh(bpy.context.active_object.data); - - - face_directions = { - 'top':[], - 'bottom':[], - 'left':[], - 'right':[], - 'front':[], - 'back':[] - } - - - print("Directions {}".format(self.directions)) - - - for face in bm.faces: - print("face {} n: {}".format(face.index, face.normal)) - # Find dominant direction - abs_x = abs(face.normal.x) - abs_y = abs(face.normal.y) - abs_z = abs(face.normal.z) - max_xyz = max(abs_x, abs_y, abs_z) - - if max_xyz == abs_x: - if face.normal.x > 0: - face_directions['right'].append(face.index) - else: - face_directions['left'].append(face.index) - elif max_xyz == abs_y: - if face.normal.y > 0: - face_directions['front'].append(face.index) - else: - face_directions['back'].append(face.index) - elif max_xyz == abs_z: - if face.normal.z > 0: - face_directions['top'].append(face.index) - else: - face_directions['bottom'].append(face.index) - - count = int(self.directions) - bpy.context.scene.texToolsSettings.color_ID_count = count - - groups = [] - # for i in range(count): - # groups.append([]) - - if self.directions == '2': - groups.append(face_directions['top']+face_directions['bottom']) - groups.append(face_directions['left']+face_directions['right']+face_directions['front']+face_directions['back']) - if self.directions == '3': - groups.append(face_directions['top']+face_directions['bottom']) - groups.append(face_directions['left']+face_directions['right']) - groups.append(face_directions['front']+face_directions['back']) - elif self.directions == '4': - groups.append(face_directions['top']) - groups.append(face_directions['left']+face_directions['right']) - groups.append(face_directions['front']+face_directions['back']) - groups.append(face_directions['bottom']) - elif self.directions == '6': - groups.append(face_directions['top']) - groups.append(face_directions['right']) - groups.append(face_directions['left']) - groups.append(face_directions['front']) - groups.append(face_directions['back']) - groups.append(face_directions['bottom']) - - # Assign Groups to colors - index_color = 0 - for group in groups: - # # rebuild bmesh data (e.g. left edit mode previous loop) - bm = bmesh.from_edit_mesh(bpy.context.active_object.data); - if hasattr(bm.faces, "ensure_lookup_table"): - bm.faces.ensure_lookup_table() - - # Select group - bpy.ops.mesh.select_all(action='DESELECT') - for index_face in group: - bm.faces[index_face].select = True - - # Assign to selection - bpy.ops.uv.textools_color_assign(index=index_color) - - index_color = (index_color+1) % bpy.context.scene.texToolsSettings.color_ID_count - - bpy.ops.object.mode_set(mode='OBJECT') - utilities_color.validate_face_colors(obj) - ''' - faces_indices_processed = [] - - - for face in bm.faces: - if face.index not in faces_indices_processed: - # Select face & extend - bpy.ops.mesh.select_all(action='DESELECT') - face.select = True - bpy.ops.mesh.select_linked(delimit={'NORMAL'}) - - faces = [f.index for f in bm.faces if (f.select and f.index not in faces_indices_processed)] - for f in faces: - faces_indices_processed.append(f) - groups.append(faces) - - - # Assign color count (caps automatically e.g. max 20) - bpy.context.scene.texToolsSettings.color_ID_count = len(groups) - gamma = 2.2 - - for i in range(bpy.context.scene.texToolsSettings.color_ID_count): - color = utilities_color.get_color_id(i, bpy.context.scene.texToolsSettings.color_ID_count) - # Fix Gamma - color[0] = pow(color[0] , gamma) - color[1] = pow(color[1] , gamma) - color[2] = pow(color[2], gamma) - utilities_color.set_color(i, color) - - # Assign Groups to colors - index_color = 0 - for group in groups: - # rebuild bmesh data (e.g. left edit mode previous loop) - bm = bmesh.from_edit_mesh(bpy.context.active_object.data); - if hasattr(bm.faces, "ensure_lookup_table"): - bm.faces.ensure_lookup_table() - - # Select group - bpy.ops.mesh.select_all(action='DESELECT') - for index_face in group: - bm.faces[index_face].select = True - - # Assign to selection - bpy.ops.uv.textools_color_assign(index=index_color) - - index_color = (index_color+1) % bpy.context.scene.texToolsSettings.color_ID_count - - bpy.ops.object.mode_set(mode='OBJECT') - utilities_color.validate_face_colors(obj) - ''' + obj = bpy.context.active_object + + # Setup Edit & Face mode + if obj.mode != 'EDIT': + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE') + + # Collect groups + bm = bmesh.from_edit_mesh(bpy.context.active_object.data); + + + face_directions = { + 'top':[], + 'bottom':[], + 'left':[], + 'right':[], + 'front':[], + 'back':[] + } + + + print("Directions {}".format(self.directions)) + + + for face in bm.faces: + print("face {} n: {}".format(face.index, face.normal)) + # Find dominant direction + abs_x = abs(face.normal.x) + abs_y = abs(face.normal.y) + abs_z = abs(face.normal.z) + max_xyz = max(abs_x, abs_y, abs_z) + + if max_xyz == abs_x: + if face.normal.x > 0: + face_directions['right'].append(face.index) + else: + face_directions['left'].append(face.index) + elif max_xyz == abs_y: + if face.normal.y > 0: + face_directions['front'].append(face.index) + else: + face_directions['back'].append(face.index) + elif max_xyz == abs_z: + if face.normal.z > 0: + face_directions['top'].append(face.index) + else: + face_directions['bottom'].append(face.index) + + count = int(self.directions) + bpy.context.scene.texToolsSettings.color_ID_count = count + + groups = [] + # for i in range(count): + # groups.append([]) + + if self.directions == '2': + groups.append(face_directions['top']+face_directions['bottom']) + groups.append(face_directions['left']+face_directions['right']+face_directions['front']+face_directions['back']) + if self.directions == '3': + groups.append(face_directions['top']+face_directions['bottom']) + groups.append(face_directions['left']+face_directions['right']) + groups.append(face_directions['front']+face_directions['back']) + elif self.directions == '4': + groups.append(face_directions['top']) + groups.append(face_directions['left']+face_directions['right']) + groups.append(face_directions['front']+face_directions['back']) + groups.append(face_directions['bottom']) + elif self.directions == '6': + groups.append(face_directions['top']) + groups.append(face_directions['right']) + groups.append(face_directions['left']) + groups.append(face_directions['front']) + groups.append(face_directions['back']) + groups.append(face_directions['bottom']) + + # Assign Groups to colors + index_color = 0 + for group in groups: + # # rebuild bmesh data (e.g. left edit mode previous loop) + bm = bmesh.from_edit_mesh(bpy.context.active_object.data); + if hasattr(bm.faces, "ensure_lookup_table"): + bm.faces.ensure_lookup_table() + + # Select group + bpy.ops.mesh.select_all(action='DESELECT') + for index_face in group: + bm.faces[index_face].select = True + + # Assign to selection + bpy.ops.uv.textools_color_assign(index=index_color) + + index_color = (index_color+1) % bpy.context.scene.texToolsSettings.color_ID_count + + bpy.ops.object.mode_set(mode='OBJECT') + utilities_color.validate_face_colors(obj) + ''' + faces_indices_processed = [] + + + for face in bm.faces: + if face.index not in faces_indices_processed: + # Select face & extend + bpy.ops.mesh.select_all(action='DESELECT') + face.select = True + bpy.ops.mesh.select_linked(delimit={'NORMAL'}) + + faces = [f.index for f in bm.faces if (f.select and f.index not in faces_indices_processed)] + for f in faces: + faces_indices_processed.append(f) + groups.append(faces) + + + # Assign color count (caps automatically e.g. max 20) + bpy.context.scene.texToolsSettings.color_ID_count = len(groups) + gamma = 2.2 + + for i in range(bpy.context.scene.texToolsSettings.color_ID_count): + color = utilities_color.get_color_id(i, bpy.context.scene.texToolsSettings.color_ID_count) + # Fix Gamma + color[0] = pow(color[0] , gamma) + color[1] = pow(color[1] , gamma) + color[2] = pow(color[2], gamma) + utilities_color.set_color(i, color) + + # Assign Groups to colors + index_color = 0 + for group in groups: + # rebuild bmesh data (e.g. left edit mode previous loop) + bm = bmesh.from_edit_mesh(bpy.context.active_object.data); + if hasattr(bm.faces, "ensure_lookup_table"): + bm.faces.ensure_lookup_table() + + # Select group + bpy.ops.mesh.select_all(action='DESELECT') + for index_face in group: + bm.faces[index_face].select = True + + # Assign to selection + bpy.ops.uv.textools_color_assign(index=index_color) + + index_color = (index_color+1) % bpy.context.scene.texToolsSettings.color_ID_count + + bpy.ops.object.mode_set(mode='OBJECT') + utilities_color.validate_face_colors(obj) + ''' bpy.utils.register_class(op) \ No newline at end of file diff --git a/op_color_from_elements.py b/op_color_from_elements.py index a713a2d..3b68ffc 100644 --- a/op_color_from_elements.py +++ b/op_color_from_elements.py @@ -8,95 +8,95 @@ from math import pi from . import utilities_color class op(bpy.types.Operator): - bl_idname = "uv.textools_color_from_elements" - bl_label = "Color Elements" - bl_description = "Assign a color ID to each mesh element" - bl_options = {'REGISTER', 'UNDO'} - + bl_idname = "uv.textools_color_from_elements" + bl_label = "Color Elements" + bl_description = "Assign a color ID to each mesh element" + bl_options = {'REGISTER', 'UNDO'} + - @classmethod - def poll(cls, context): - if not bpy.context.active_object: - return False + @classmethod + def poll(cls, context): + if not bpy.context.active_object: + return False - if bpy.context.active_object not in bpy.context.selected_objects: - return False + if bpy.context.active_object not in bpy.context.selected_objects: + return False - if len(bpy.context.selected_objects) != 1: - return False + if len(bpy.context.selected_objects) != 1: + return False - if bpy.context.active_object.type != 'MESH': - return False + if bpy.context.active_object.type != 'MESH': + return False - #Only in UV editor mode - if bpy.context.area.type != 'IMAGE_EDITOR': - return False + #Only in UV editor mode + if bpy.context.area.type != 'IMAGE_EDITOR': + return False - return True - - def execute(self, context): - color_elements(self, context) - return {'FINISHED'} + return True + + def execute(self, context): + color_elements(self, context) + return {'FINISHED'} def color_elements(self, context): - obj = bpy.context.active_object - - # Setup Edit & Face mode - if obj.mode != 'EDIT': - bpy.ops.object.mode_set(mode='EDIT') - bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE') - - # Collect groups - bm = bmesh.from_edit_mesh(bpy.context.active_object.data); - faces_indices_processed = [] - groups = [] - - for face in bm.faces: - if face.index not in faces_indices_processed: - # Select face & extend - bpy.ops.mesh.select_all(action='DESELECT') - face.select = True - bpy.ops.mesh.select_linked(delimit={'NORMAL'}) - - faces = [f.index for f in bm.faces if (f.select and f.index not in faces_indices_processed)] - for f in faces: - faces_indices_processed.append(f) - groups.append(faces) - - - # Assign color count (caps automatically e.g. max 20) - bpy.context.scene.texToolsSettings.color_ID_count = len(groups) - gamma = 2.2 - - for i in range(bpy.context.scene.texToolsSettings.color_ID_count): - color = utilities_color.get_color_id(i, bpy.context.scene.texToolsSettings.color_ID_count) - # Fix Gamma - color[0] = pow(color[0] , gamma) - color[1] = pow(color[1] , gamma) - color[2] = pow(color[2], gamma) - utilities_color.set_color(i, color) - - # Assign Groups to colors - index_color = 0 - for group in groups: - # rebuild bmesh data (e.g. left edit mode previous loop) - bm = bmesh.from_edit_mesh(bpy.context.active_object.data); - if hasattr(bm.faces, "ensure_lookup_table"): - bm.faces.ensure_lookup_table() - - # Select group - bpy.ops.mesh.select_all(action='DESELECT') - for index_face in group: - bm.faces[index_face].select = True - - # Assign to selection - bpy.ops.uv.textools_color_assign(index=index_color) - - index_color = (index_color+1) % bpy.context.scene.texToolsSettings.color_ID_count - - bpy.ops.object.mode_set(mode='OBJECT') - utilities_color.validate_face_colors(obj) + obj = bpy.context.active_object + + # Setup Edit & Face mode + if obj.mode != 'EDIT': + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE') + + # Collect groups + bm = bmesh.from_edit_mesh(bpy.context.active_object.data); + faces_indices_processed = [] + groups = [] + + for face in bm.faces: + if face.index not in faces_indices_processed: + # Select face & extend + bpy.ops.mesh.select_all(action='DESELECT') + face.select = True + bpy.ops.mesh.select_linked(delimit={'NORMAL'}) + + faces = [f.index for f in bm.faces if (f.select and f.index not in faces_indices_processed)] + for f in faces: + faces_indices_processed.append(f) + groups.append(faces) + + + # Assign color count (caps automatically e.g. max 20) + bpy.context.scene.texToolsSettings.color_ID_count = len(groups) + gamma = 2.2 + + for i in range(bpy.context.scene.texToolsSettings.color_ID_count): + color = utilities_color.get_color_id(i, bpy.context.scene.texToolsSettings.color_ID_count) + # Fix Gamma + color[0] = pow(color[0] , gamma) + color[1] = pow(color[1] , gamma) + color[2] = pow(color[2], gamma) + utilities_color.set_color(i, color) + + # Assign Groups to colors + index_color = 0 + for group in groups: + # rebuild bmesh data (e.g. left edit mode previous loop) + bm = bmesh.from_edit_mesh(bpy.context.active_object.data); + if hasattr(bm.faces, "ensure_lookup_table"): + bm.faces.ensure_lookup_table() + + # Select group + bpy.ops.mesh.select_all(action='DESELECT') + for index_face in group: + bm.faces[index_face].select = True + + # Assign to selection + bpy.ops.uv.textools_color_assign(index=index_color) + + index_color = (index_color+1) % bpy.context.scene.texToolsSettings.color_ID_count + + bpy.ops.object.mode_set(mode='OBJECT') + utilities_color.validate_face_colors(obj) bpy.utils.register_class(op) \ No newline at end of file diff --git a/op_color_from_materials.py b/op_color_from_materials.py index 61d7988..e91d601 100644 --- a/op_color_from_materials.py +++ b/op_color_from_materials.py @@ -8,46 +8,46 @@ from math import pi from . import utilities_color class op(bpy.types.Operator): - bl_idname = "uv.textools_color_from_materials" - bl_label = "Color Elements" - bl_description = "Assign a color ID to each mesh material slot" - bl_options = {'REGISTER', 'UNDO'} - + bl_idname = "uv.textools_color_from_materials" + bl_label = "Color Elements" + bl_description = "Assign a color ID to each mesh material slot" + bl_options = {'REGISTER', 'UNDO'} + - @classmethod - def poll(cls, context): - if not bpy.context.active_object: - return False + @classmethod + def poll(cls, context): + if not bpy.context.active_object: + return False - if bpy.context.active_object not in bpy.context.selected_objects: - return False + if bpy.context.active_object not in bpy.context.selected_objects: + return False - if len(bpy.context.selected_objects) != 1: - return False + if len(bpy.context.selected_objects) != 1: + return False - if bpy.context.active_object.type != 'MESH': - return False + if bpy.context.active_object.type != 'MESH': + return False - #Only in UV editor mode - if bpy.context.area.type != 'IMAGE_EDITOR': - return False + #Only in UV editor mode + if bpy.context.area.type != 'IMAGE_EDITOR': + return False - return True - - def execute(self, context): - color_materials(self, context) - return {'FINISHED'} + return True + + def execute(self, context): + color_materials(self, context) + return {'FINISHED'} def color_materials(self, context): - obj = bpy.context.active_object - - for s in range(len(obj.material_slots)): - slot = obj.material_slots[s] - if slot.material: - utilities_color.assign_slot(obj, s) - - utilities_color.validate_face_colors(obj) + obj = bpy.context.active_object + + for s in range(len(obj.material_slots)): + slot = obj.material_slots[s] + if slot.material: + utilities_color.assign_slot(obj, s) + + utilities_color.validate_face_colors(obj) bpy.utils.register_class(op) \ No newline at end of file diff --git a/op_color_io_export.py b/op_color_io_export.py index 46cc5f8..f3d9457 100644 --- a/op_color_io_export.py +++ b/op_color_io_export.py @@ -9,34 +9,34 @@ from . import utilities_color class op(bpy.types.Operator): - bl_idname = "uv.textools_color_io_export" - bl_label = "Export" - bl_description = "Export current color palette to clipboard" + bl_idname = "uv.textools_color_io_export" + bl_label = "Export" + bl_description = "Export current color palette to clipboard" - @classmethod - def poll(cls, context): - - #Only in UV editor mode - if bpy.context.area.type != 'IMAGE_EDITOR': - return False + @classmethod + def poll(cls, context): + + #Only in UV editor mode + if bpy.context.area.type != 'IMAGE_EDITOR': + return False - return True - - def execute(self, context): - export_colors(self, context) - return {'FINISHED'} + return True + + def execute(self, context): + export_colors(self, context) + return {'FINISHED'} def export_colors(self, context): - - hex_colors = [] - for i in range(bpy.context.scene.texToolsSettings.color_ID_count): - color = getattr(bpy.context.scene.texToolsSettings, "color_ID_color_{}".format(i)) - hex_colors.append( utilities_color.color_to_hex( color) ) - - bpy.context.window_manager.clipboard = ", ".join(hex_colors) - bpy.ops.ui.textools_popup('INVOKE_DEFAULT', message="{}x colors copied to clipboard".format(len(hex_colors))) + + hex_colors = [] + for i in range(bpy.context.scene.texToolsSettings.color_ID_count): + color = getattr(bpy.context.scene.texToolsSettings, "color_ID_color_{}".format(i)) + hex_colors.append( utilities_color.color_to_hex( color) ) + + bpy.context.window_manager.clipboard = ", ".join(hex_colors) + bpy.ops.ui.textools_popup('INVOKE_DEFAULT', message="{}x colors copied to clipboard".format(len(hex_colors))) bpy.utils.register_class(op) \ No newline at end of file diff --git a/op_color_io_import.py b/op_color_io_import.py index 5e57a43..ad06786 100644 --- a/op_color_io_import.py +++ b/op_color_io_import.py @@ -9,53 +9,53 @@ from math import pi from . import utilities_color class op(bpy.types.Operator): - bl_idname = "uv.textools_color_io_import" - bl_label = "Import" - bl_description = "Import hex colors from the clipboard as current color palette" + bl_idname = "uv.textools_color_io_import" + bl_label = "Import" + bl_description = "Import hex colors from the clipboard as current color palette" - @classmethod - def poll(cls, context): - + @classmethod + def poll(cls, context): + - #Only in UV editor mode - if bpy.context.area.type != 'IMAGE_EDITOR': - return False + #Only in UV editor mode + if bpy.context.area.type != 'IMAGE_EDITOR': + return False - return True - - def execute(self, context): - import_colors(self, context) - return {'FINISHED'} + return True + + def execute(self, context): + import_colors(self, context) + return {'FINISHED'} def import_colors(self, context): - # Clipboard hex strings - hex_strings = bpy.context.window_manager.clipboard.split(',') - - for i in range(len(hex_strings)): - hex_strings[i] = hex_strings[i].strip().strip('#') - if len(hex_strings[i]) != 6 or not all(c in string.hexdigits for c in hex_strings[i]): - # Incorrect format - self.report({'ERROR_INVALID_INPUT'}, "Incorrect hex format '{}' use a #RRGGBB pattern".format(hex_strings[i])) - return - else: - name = "color_ID_color_{}".format(i) - if hasattr(bpy.context.scene.texToolsSettings, name): - # Color Index exists - color = utilities_color.hex_to_color( hex_strings[i] ) - setattr(bpy.context.scene.texToolsSettings, name, color) - else: - # More colors imported than supported - self.report({'ERROR_INVALID_INPUT'}, "Only {}x colors have been imported instead of {}x".format( - i,len(hex_strings) - )) - return - - # Set number of colors - bpy.context.scene.texToolsSettings.color_ID_count = len(hex_strings) - - bpy.ops.ui.textools_popup('INVOKE_DEFAULT', message="{}x colors imported from clipboard".format( len(hex_strings) )) + # Clipboard hex strings + hex_strings = bpy.context.window_manager.clipboard.split(',') + + for i in range(len(hex_strings)): + hex_strings[i] = hex_strings[i].strip().strip('#') + if len(hex_strings[i]) != 6 or not all(c in string.hexdigits for c in hex_strings[i]): + # Incorrect format + self.report({'ERROR_INVALID_INPUT'}, "Incorrect hex format '{}' use a #RRGGBB pattern".format(hex_strings[i])) + return + else: + name = "color_ID_color_{}".format(i) + if hasattr(bpy.context.scene.texToolsSettings, name): + # Color Index exists + color = utilities_color.hex_to_color( hex_strings[i] ) + setattr(bpy.context.scene.texToolsSettings, name, color) + else: + # More colors imported than supported + self.report({'ERROR_INVALID_INPUT'}, "Only {}x colors have been imported instead of {}x".format( + i,len(hex_strings) + )) + return + + # Set number of colors + bpy.context.scene.texToolsSettings.color_ID_count = len(hex_strings) + + bpy.ops.ui.textools_popup('INVOKE_DEFAULT', message="{}x colors imported from clipboard".format( len(hex_strings) )) bpy.utils.register_class(op) diff --git a/op_color_select.py b/op_color_select.py index 15a2c23..3b1b89d 100644 --- a/op_color_select.py +++ b/op_color_select.py @@ -8,62 +8,62 @@ from math import pi from . import utilities_color class op(bpy.types.Operator): - bl_idname = "uv.textools_color_select" - bl_label = "Assign Color" - bl_description = "Select faces by this color" - bl_options = {'REGISTER', 'UNDO'} - - index : bpy.props.IntProperty(description="Color Index", default=0) + bl_idname = "uv.textools_color_select" + bl_label = "Assign Color" + bl_description = "Select faces by this color" + bl_options = {'REGISTER', 'UNDO'} + + index : bpy.props.IntProperty(description="Color Index", default=0) - @classmethod - def poll(cls, context): - if not bpy.context.active_object: - return False + @classmethod + def poll(cls, context): + if not bpy.context.active_object: + return False - if bpy.context.active_object not in bpy.context.selected_objects: - return False + if bpy.context.active_object not in bpy.context.selected_objects: + return False - # Allow only 1 object selected - if len(bpy.context.selected_objects) != 1: - return False + # Allow only 1 object selected + if len(bpy.context.selected_objects) != 1: + return False - if bpy.context.active_object.type != 'MESH': - return False + if bpy.context.active_object.type != 'MESH': + return False - #Only in UV editor mode - if bpy.context.area.type != 'IMAGE_EDITOR': - return False + #Only in UV editor mode + if bpy.context.area.type != 'IMAGE_EDITOR': + return False - return True - - def execute(self, context): - select_color(self, context, self.index) - return {'FINISHED'} + return True + + def execute(self, context): + select_color(self, context, self.index) + return {'FINISHED'} def select_color(self, context, index): - print("Color select "+str(index) ) - - obj = bpy.context.active_object - - # Check for missing slots, materials,.. - if index >= len(obj.material_slots): - self.report({'ERROR_INVALID_INPUT'}, "No material slot for color '{}' found".format(index) ) - return - - if not obj.material_slots[index].material: - self.report({'ERROR_INVALID_INPUT'}, "No material found for material slot '{}'".format(index) ) - return - - if bpy.context.active_object.mode != 'EDIT': - bpy.ops.object.mode_set(mode='EDIT') - - # Select faces - bm = bmesh.from_edit_mesh(bpy.context.active_object.data); - bpy.ops.mesh.select_all(action='DESELECT') - for face in bm.faces: - if face.material_index == index: - face.select = True + print("Color select "+str(index) ) + + obj = bpy.context.active_object + + # Check for missing slots, materials,.. + if index >= len(obj.material_slots): + self.report({'ERROR_INVALID_INPUT'}, "No material slot for color '{}' found".format(index) ) + return + + if not obj.material_slots[index].material: + self.report({'ERROR_INVALID_INPUT'}, "No material found for material slot '{}'".format(index) ) + return + + if bpy.context.active_object.mode != 'EDIT': + bpy.ops.object.mode_set(mode='EDIT') + + # Select faces + bm = bmesh.from_edit_mesh(bpy.context.active_object.data); + bpy.ops.mesh.select_all(action='DESELECT') + for face in bm.faces: + if face.material_index == index: + face.select = True bpy.utils.register_class(op) \ No newline at end of file diff --git a/op_edge_split_bevel.py b/op_edge_split_bevel.py index 0a36a24..423fa4d 100644 --- a/op_edge_split_bevel.py +++ b/op_edge_split_bevel.py @@ -14,276 +14,276 @@ from . import utilities_uv class op(bpy.types.Operator): - bl_idname = "uv.textools_edge_split_bevel" - bl_label = "Split Bevel" - bl_description = "..." - bl_options = {'REGISTER', 'UNDO'} + bl_idname = "uv.textools_edge_split_bevel" + bl_label = "Split Bevel" + bl_description = "..." + bl_options = {'REGISTER', 'UNDO'} - radius : bpy.props.FloatProperty( - name = "Space", - description = "Space for split bevel", - default = 0.015, - min = 0, - max = 0.35 - ) + radius : bpy.props.FloatProperty( + name = "Space", + description = "Space for split bevel", + default = 0.015, + min = 0, + max = 0.35 + ) - @classmethod - def poll(cls, context): - if not bpy.context.active_object: - return False + @classmethod + def poll(cls, context): + if not bpy.context.active_object: + return False - #Only in Edit mode - if bpy.context.active_object.mode != 'EDIT': - return False + #Only in Edit mode + if bpy.context.active_object.mode != 'EDIT': + return False - #Requires UV map - if not bpy.context.object.data.uv_layers: - return False + #Requires UV map + if not bpy.context.object.data.uv_layers: + return False - if bpy.context.scene.tool_settings.use_uv_select_sync: - return False + if bpy.context.scene.tool_settings.use_uv_select_sync: + return False - #Only in UV editor mode - if bpy.context.area.type != 'IMAGE_EDITOR': - return False + #Only in UV editor mode + if bpy.context.area.type != 'IMAGE_EDITOR': + return False - return True + return True - def execute(self, context): - main(self, self.radius) - return {'FINISHED'} + def execute(self, context): + main(self, self.radius) + return {'FINISHED'} def main(self, radius): - #Store selection - utilities_uv.selection_store() + #Store selection + utilities_uv.selection_store() - print("____________\nedge split UV sharp edges {}".format(radius)) + print("____________\nedge split UV sharp edges {}".format(radius)) - obj = bpy.context.object - bm = bmesh.from_edit_mesh(bpy.context.active_object.data); - uv_layers = bm.loops.layers.uv.verify(); - - islands = utilities_uv.getSelectionIslands() - + obj = bpy.context.object + bm = bmesh.from_edit_mesh(bpy.context.active_object.data); + uv_layers = bm.loops.layers.uv.verify(); + + islands = utilities_uv.getSelectionIslands() + - # Collect UV to Vert - vert_to_uv = utilities_uv.get_vert_to_uv(bm, uv_layers) - uv_to_vert = utilities_uv.get_uv_to_vert(bm, uv_layers) + # Collect UV to Vert + vert_to_uv = utilities_uv.get_vert_to_uv(bm, uv_layers) + uv_to_vert = utilities_uv.get_uv_to_vert(bm, uv_layers) - # Collect hard edges - edges = [] - for edge in bm.edges: - if edge.select and not edge.smooth: - # edge.link_faces - # print("Hard edge: {} - {}".format(edge.verts[0].index, edge.verts[1].index)) - edges.append(edge) + # Collect hard edges + edges = [] + for edge in bm.edges: + if edge.select and not edge.smooth: + # edge.link_faces + # print("Hard edge: {} - {}".format(edge.verts[0].index, edge.verts[1].index)) + edges.append(edge) - # Get vert rails to slide - vert_rails = get_vert_edge_rails(edges) + # Get vert rails to slide + vert_rails = get_vert_edge_rails(edges) - # Get left and right faces - edge_face_pairs = get_edge_face_pairs(edges) + # Get left and right faces + edge_face_pairs = get_edge_face_pairs(edges) - print("Vert rails: {}x".format(len(vert_rails))) - # for vert in vert_rails: - # print(".. v.idx {} = {}x".format(vert.index, len(vert_rails[vert]) )) + print("Vert rails: {}x".format(len(vert_rails))) + # for vert in vert_rails: + # print(".. v.idx {} = {}x".format(vert.index, len(vert_rails[vert]) )) - vert_processed = [] - vert_uv_pos = [] + vert_processed = [] + vert_uv_pos = [] - for edge in edges: - if len(edge_face_pairs[edge]) == 2: - v0 = edge.verts[0] - v1 = edge.verts[1] - - f0 = edge_face_pairs[edge][0] - f1 = edge_face_pairs[edge][1] + for edge in edges: + if len(edge_face_pairs[edge]) == 2: + v0 = edge.verts[0] + v1 = edge.verts[1] + + f0 = edge_face_pairs[edge][0] + f1 = edge_face_pairs[edge][1] - # v0 - if v0 not in vert_processed: - vert_processed.append(v0) - faces, origin, delta = slide_uvs(v0, edge, f0, edges, vert_rails, vert_to_uv) - vert_uv_pos.append( {"v":v0, "f":f0, "origin":origin, "delta":delta, "faces":faces} ) + # v0 + if v0 not in vert_processed: + vert_processed.append(v0) + faces, origin, delta = slide_uvs(v0, edge, f0, edges, vert_rails, vert_to_uv) + vert_uv_pos.append( {"v":v0, "f":f0, "origin":origin, "delta":delta, "faces":faces} ) - faces, origin, delta = slide_uvs(v0, edge, f1, edges, vert_rails, vert_to_uv) - vert_uv_pos.append( {"v":v0, "f":f1, "origin":origin, "delta":delta, "faces":faces} ) + faces, origin, delta = slide_uvs(v0, edge, f1, edges, vert_rails, vert_to_uv) + vert_uv_pos.append( {"v":v0, "f":f1, "origin":origin, "delta":delta, "faces":faces} ) - # V1 - if v1 not in vert_processed: - vert_processed.append(v1) - faces, origin, delta = slide_uvs(v1, edge, f0, edges, vert_rails, vert_to_uv) - vert_uv_pos.append( {"v":v1, "f":f0, "origin":origin, "delta":delta, "faces":faces} ) - - faces, origin, delta = slide_uvs(v1, edge, f1, edges, vert_rails, vert_to_uv) - vert_uv_pos.append( {"v":v1, "f":f1, "origin":origin, "delta":delta, "faces":faces} ) - - # ... - for item in vert_uv_pos: - v = item["v"] + # V1 + if v1 not in vert_processed: + vert_processed.append(v1) + faces, origin, delta = slide_uvs(v1, edge, f0, edges, vert_rails, vert_to_uv) + vert_uv_pos.append( {"v":v1, "f":f0, "origin":origin, "delta":delta, "faces":faces} ) + + faces, origin, delta = slide_uvs(v1, edge, f1, edges, vert_rails, vert_to_uv) + vert_uv_pos.append( {"v":v1, "f":f1, "origin":origin, "delta":delta, "faces":faces} ) + + # ... + for item in vert_uv_pos: + v = item["v"] - - for face in item["faces"]: - if v in face.verts: - for loop in face.loops: - if loop.vert == v: - loop[uv_layers].uv= item["origin"] + item["delta"] * (radius/2) - # for f in faces: - # for loop in f.loops: - # if loop.vert == vert: - # loop[uv_layers].uv= vert_to_uv[vert][0].uv + item["delta"] * radius/2 + + for face in item["faces"]: + if v in face.verts: + for loop in face.loops: + if loop.vert == v: + loop[uv_layers].uv= item["origin"] + item["delta"] * (radius/2) + # for f in faces: + # for loop in f.loops: + # if loop.vert == vert: + # loop[uv_layers].uv= vert_to_uv[vert][0].uv + item["delta"] * radius/2 - + - # for loop in face.loops: - # if loop.vert == vert: - # loop[uv_layers].uv+= avg_uv_delta + # for loop in face.loops: + # if loop.vert == vert: + # loop[uv_layers].uv+= avg_uv_delta - #Restore selection - utilities_uv.selection_restore() + #Restore selection + utilities_uv.selection_restore() def slide_uvs(vert, edge, face, edges, vert_rails, vert_to_uv): - - def IS_DEBUG(): - return vert.index == 64 and edge.verts[0].index == 64 and edge.verts[1].index == 63 - - - A = edge.verts[0] - B = edge.verts[1] - A_links, B_links = get_edge_prev_next(edge, edges) - - verts_edges = {edge.verts[0], edge.verts[1]} - for v in A_links: - verts_edges.add( v ) - for v in B_links: - verts_edges.add( v ) + + def IS_DEBUG(): + return vert.index == 64 and edge.verts[0].index == 64 and edge.verts[1].index == 63 + + + A = edge.verts[0] + B = edge.verts[1] + A_links, B_links = get_edge_prev_next(edge, edges) + + verts_edges = {edge.verts[0], edge.verts[1]} + for v in A_links: + verts_edges.add( v ) + for v in B_links: + verts_edges.add( v ) - if IS_DEBUG(): - print("\r") + if IS_DEBUG(): + print("\r") - print("Edge {} <--> {} ({})".format(edge.verts[0].index, edge.verts[1].index , vert.index)) + print("Edge {} <--> {} ({})".format(edge.verts[0].index, edge.verts[1].index , vert.index)) - # Collect faces of this side + # Collect faces of this side - ''' - faces = [face] - face_edges_used = [e for e in face.edges if e in edges] - for e in face.edges: - if e not in face_edges_used: - for f in e.link_faces: - if f != face: - faces.append(f) - ''' + ''' + faces = [face] + face_edges_used = [e for e in face.edges if e in edges] + for e in face.edges: + if e not in face_edges_used: + for f in e.link_faces: + if f != face: + faces.append(f) + ''' - faces = [face] - edges_main_used = [edge] - for i in range(2): - append = [] + faces = [face] + edges_main_used = [edge] + for i in range(2): + append = [] - for f in faces: - for e in f.edges: - if e not in edges_main_used: - if e in edges: - edges_main_used.append(e) + for f in faces: + for e in f.edges: + if e not in edges_main_used: + if e in edges: + edges_main_used.append(e) - for f_link in e.link_faces: - if f_link not in faces: - append.append(f_link) - faces.extend(append) + for f_link in e.link_faces: + if f_link not in faces: + append.append(f_link) + faces.extend(append) - if IS_DEBUG(): - print(" Faces {}x = {}".format(len(faces), [f.index for f in faces])) + if IS_DEBUG(): + print(" Faces {}x = {}".format(len(faces), [f.index for f in faces])) - # Get all face edges that could be valid rails - face_edges = list(set([e for f in faces for e in f.edges if e not in edges])) + # Get all face edges that could be valid rails + face_edges = list(set([e for f in faces for e in f.edges if e not in edges])) - # The verts influencing the offset - verts = [A,B] - if vert == A: - verts.extend(B_links) - elif vert == B: - verts.extend(A_links) - # verts = [vert] + # The verts influencing the offset + verts = [A,B] + if vert == A: + verts.extend(B_links) + elif vert == B: + verts.extend(A_links) + # verts = [vert] - if IS_DEBUG(): - print(" Verts: {}x = {}".format(len(verts), [v.index for v in verts])) - print(" Rails:") + if IS_DEBUG(): + print(" Verts: {}x = {}".format(len(verts), [v.index for v in verts])) + print(" Rails:") - delta = Vector((0,0)) - count = 0.0 - for v in verts: - rails = [e for e in vert_rails[v] if e in face_edges] + delta = Vector((0,0)) + count = 0.0 + for v in verts: + rails = [e for e in vert_rails[v] if e in face_edges] - if IS_DEBUG(): - print(" #{} rails = {}".format(v.index, [("{} - {}".format(e.verts[0].index, e.verts[1].index)) for e in rails])) + if IS_DEBUG(): + print(" #{} rails = {}".format(v.index, [("{} - {}".format(e.verts[0].index, e.verts[1].index)) for e in rails])) - for e in rails: - # determine order - v0 = None - v1 = None - if e.verts[0] in verts_edges: - v0 = e.verts[0] - v1 = e.verts[1] - elif e.verts[1] in verts_edges: - v0 = e.verts[1] - v1 = e.verts[0] - uv0 = vert_to_uv[v0][0].uv - uv1 = vert_to_uv[v1][0].uv - delta += (uv1-uv0).normalized() - count += 1.0 + for e in rails: + # determine order + v0 = None + v1 = None + if e.verts[0] in verts_edges: + v0 = e.verts[0] + v1 = e.verts[1] + elif e.verts[1] in verts_edges: + v0 = e.verts[1] + v1 = e.verts[0] + uv0 = vert_to_uv[v0][0].uv + uv1 = vert_to_uv[v1][0].uv + delta += (uv1-uv0).normalized() + count += 1.0 - delta/=count + delta/=count - if IS_DEBUG(): - print("\r") + if IS_DEBUG(): + print("\r") - return faces, vert_to_uv[vert][0].uv.copy(), delta.normalized() - # print(" V{} = {}".format(v.index, avg_uv_delta)) + return faces, vert_to_uv[vert][0].uv.copy(), delta.normalized() + # print(" V{} = {}".format(v.index, avg_uv_delta)) - # for loop in face.loops: - # if loop.vert == vert: - # loop[uv_layers].uv+= avg_uv_delta + # for loop in face.loops: + # if loop.vert == vert: + # loop[uv_layers].uv+= avg_uv_delta ''' def slide_face_uvs(uv_layers, edge, vert, face, radius, vert_to_uv): - avg_target = Vector((0,0)) - avg_count = 0 - - for e in face.edges: - if e != edge and vert in e.verts: - vert_B = e.verts[0] - if vert == e.verts[0]: - vert_B = e.verts[1] - A = vert_to_uv[vert][0].uv - B = vert_to_uv[vert_B][0].uv - - avg_target+= A +(B - A).normalized() * radius - avg_count+=1 - - avg_target/=avg_count - avg_target = vert_to_uv[vert][0].uv +(avg_target - vert_to_uv[vert][0].uv).normalized() * radius - - for loop in face.loops: - if loop.vert == vert: - loop[uv_layers].uv = avg_target + avg_target = Vector((0,0)) + avg_count = 0 + + for e in face.edges: + if e != edge and vert in e.verts: + vert_B = e.verts[0] + if vert == e.verts[0]: + vert_B = e.verts[1] + A = vert_to_uv[vert][0].uv + B = vert_to_uv[vert_B][0].uv + + avg_target+= A +(B - A).normalized() * radius + avg_count+=1 + + avg_target/=avg_count + avg_target = vert_to_uv[vert][0].uv +(avg_target - vert_to_uv[vert][0].uv).normalized() * radius + + for loop in face.loops: + if loop.vert == vert: + loop[uv_layers].uv = avg_target ''' @@ -294,82 +294,82 @@ def slide_face_uvs(uv_layers, edge, vert, face, radius, vert_to_uv): ''' - # Get all rails (max 3x: current, before and after) - rails = [e for v in verts_edges for e in vert_rails[v]] + # Get all rails (max 3x: current, before and after) + rails = [e for v in verts_edges for e in vert_rails[v]] - for x in rails: - print(" raail: {} x {}".format(x.verts[0].index, x.verts[1].index)) + for x in rails: + print(" raail: {} x {}".format(x.verts[0].index, x.verts[1].index)) - # Keep only rails shared with faces - rails = [e for e in rails if e in face_edges] + # Keep only rails shared with faces + rails = [e for e in rails if e in face_edges] - # print("...... v{} with {}x rails ".format(vert.index, len(rails))) + # print("...... v{} with {}x rails ".format(vert.index, len(rails))) - # Filter rails on same side + # Filter rails on same side ''' def get_edge_prev_next(edge, edges): - A = edge.verts[0] - B = edge.verts[1] + A = edge.verts[0] + B = edge.verts[1] - # print(" get_edge_prev_next {}x edges".format(len(edges))) - # v0_extends = [] - # v0_extends = [v for e in edges for v in e.verts if v in edge.verts and e != edge and v != v0] - # v1_extends = [v for e in edges for v in e.verts if v in edge.verts and e != edge and v != v1] - # v0_extends = [v_nest for v in edge.verts for e in v.link_edges for v_nest in e.verts if e != edge and if e in edges] - - A_extends = [v2 for v1 in edge.verts for e in v1.link_edges for v2 in e.verts if e != edge and e in edges and v2 not in edge.verts and v1 != A] - B_extends = [v2 for v1 in edge.verts for e in v1.link_edges for v2 in e.verts if e != edge and e in edges and v2 not in edge.verts and v1 != B] + # print(" get_edge_prev_next {}x edges".format(len(edges))) + # v0_extends = [] + # v0_extends = [v for e in edges for v in e.verts if v in edge.verts and e != edge and v != v0] + # v1_extends = [v for e in edges for v in e.verts if v in edge.verts and e != edge and v != v1] + # v0_extends = [v_nest for v in edge.verts for e in v.link_edges for v_nest in e.verts if e != edge and if e in edges] + + A_extends = [v2 for v1 in edge.verts for e in v1.link_edges for v2 in e.verts if e != edge and e in edges and v2 not in edge.verts and v1 != A] + B_extends = [v2 for v1 in edge.verts for e in v1.link_edges for v2 in e.verts if e != edge and e in edges and v2 not in edge.verts and v1 != B] - return A_extends, B_extends + return A_extends, B_extends def get_edge_face_pairs(edges): - edge_faces = {} - for edge in edges: - v0 = edge.verts[0] - v1 = edge.verts[1] - faces = [] - for face in edge.link_faces: - if v0 in face.verts and v1 in face.verts: - faces.append(face) - edge_faces[edge] = faces + edge_faces = {} + for edge in edges: + v0 = edge.verts[0] + v1 = edge.verts[1] + faces = [] + for face in edge.link_faces: + if v0 in face.verts and v1 in face.verts: + faces.append(face) + edge_faces[edge] = faces - return edge_faces + return edge_faces def get_vert_edge_rails(edges): - vert_rails = {} - for edge in edges: - v0 = edge.verts[0] - v1 = edge.verts[1] + vert_rails = {} + for edge in edges: + v0 = edge.verts[0] + v1 = edge.verts[1] - faces = [] - for face in edge.link_faces: - if v0 in face.verts and v1 in face.verts: - faces.append(face) + faces = [] + for face in edge.link_faces: + if v0 in face.verts and v1 in face.verts: + faces.append(face) - for face in faces: - for e in face.edges: - if e not in edges and len(e.link_faces) > 0: - if v0 not in vert_rails: - vert_rails[ v0 ] = [] - if v1 not in vert_rails: - vert_rails[ v1 ] = [] + for face in faces: + for e in face.edges: + if e not in edges and len(e.link_faces) > 0: + if v0 not in vert_rails: + vert_rails[ v0 ] = [] + if v1 not in vert_rails: + vert_rails[ v1 ] = [] - if v0 in e.verts and e not in vert_rails[v0]: - vert_rails[v0].append(e) + if v0 in e.verts and e not in vert_rails[v0]: + vert_rails[v0].append(e) - if v1 in e.verts and e not in vert_rails[v1]: - vert_rails[v1].append(e) + if v1 in e.verts and e not in vert_rails[v1]: + vert_rails[v1].append(e) - return vert_rails + return vert_rails bpy.utils.register_class(op) diff --git a/op_island_align_edge.py b/op_island_align_edge.py index 13eeadc..90d9ef9 100644 --- a/op_island_align_edge.py +++ b/op_island_align_edge.py @@ -9,134 +9,134 @@ from math import pi from . import utilities_uv class op(bpy.types.Operator): - bl_idname = "uv.textools_island_align_edge" - bl_label = "Align Island by Edge" - bl_description = "Align the island by selected edge" - bl_options = {'REGISTER', 'UNDO'} - - @classmethod - def poll(cls, context): - #Only in UV editor mode - if bpy.context.area.type != 'IMAGE_EDITOR': - return False + bl_idname = "uv.textools_island_align_edge" + bl_label = "Align Island by Edge" + bl_description = "Align the island by selected edge" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(cls, context): + #Only in UV editor mode + if bpy.context.area.type != 'IMAGE_EDITOR': + return False - if not bpy.context.active_object: - return False + if not bpy.context.active_object: + return False - if bpy.context.active_object.type != 'MESH': - return False + if bpy.context.active_object.type != 'MESH': + return False - #Only in Edit mode - if bpy.context.active_object.mode != 'EDIT': - return False + #Only in Edit mode + if bpy.context.active_object.mode != 'EDIT': + return False - if bpy.context.scene.tool_settings.use_uv_select_sync: - return False + if bpy.context.scene.tool_settings.use_uv_select_sync: + return False - #Requires UV map - if not bpy.context.object.data.uv_layers: - return False + #Requires UV map + if not bpy.context.object.data.uv_layers: + return False - # Requires UV Edge select mode - if bpy.context.scene.tool_settings.uv_select_mode != 'EDGE': - return False + # Requires UV Edge select mode + if bpy.context.scene.tool_settings.uv_select_mode != 'EDGE': + return False - return True + return True - def execute(self, context): - #Store selection - utilities_uv.selection_store() + def execute(self, context): + #Store selection + utilities_uv.selection_store() - main(context) + main(context) - #Restore selection - utilities_uv.selection_restore() + #Restore selection + utilities_uv.selection_restore() - return {'FINISHED'} + return {'FINISHED'} def main(context): - print("Executing operator_island_align_edge") - - bm = bmesh.from_edit_mesh(bpy.context.active_object.data) - uv_layers = bm.loops.layers.uv.verify() - - faces_selected = []; - for face in bm.faces: - if face.select: - for loop in face.loops: - if loop[uv_layers].select: - faces_selected.append(face) - break - - print("faces_selected: "+str(len(faces_selected))) - - # Collect 2 uv verts for each island - face_uvs = {} - for face in faces_selected: - uvs = [] - for loop in face.loops: - if loop[uv_layers].select: - uvs.append(loop[uv_layers]) - if len(uvs) >= 2: - break - if len(uvs) >= 2: - face_uvs[face] = uvs - - faces_islands = {} - faces_unparsed = faces_selected.copy() - for face in face_uvs: - if face in faces_unparsed: - - bpy.ops.uv.select_all(action='DESELECT') - face_uvs[face][0].select = True; - bpy.ops.uv.select_linked()#Extend selection - - #Collect faces - faces_island = [face]; - for f in faces_unparsed: - if f != face and f.select and f.loops[0][uv_layers].select: - print("append "+str(f.index)) - faces_island.append(f) - for f in faces_island: - faces_unparsed.remove(f) - - #Assign Faces to island - faces_islands[face] = faces_island - - print("Sets: {}x".format(len(faces_islands))) - - # Align each island to its edges - for face in faces_islands: - align_island(face_uvs[face][0].uv, face_uvs[face][1].uv, faces_islands[face]) + print("Executing operator_island_align_edge") + + bm = bmesh.from_edit_mesh(bpy.context.active_object.data) + uv_layers = bm.loops.layers.uv.verify() + + faces_selected = []; + for face in bm.faces: + if face.select: + for loop in face.loops: + if loop[uv_layers].select: + faces_selected.append(face) + break + + print("faces_selected: "+str(len(faces_selected))) + + # Collect 2 uv verts for each island + face_uvs = {} + for face in faces_selected: + uvs = [] + for loop in face.loops: + if loop[uv_layers].select: + uvs.append(loop[uv_layers]) + if len(uvs) >= 2: + break + if len(uvs) >= 2: + face_uvs[face] = uvs + + faces_islands = {} + faces_unparsed = faces_selected.copy() + for face in face_uvs: + if face in faces_unparsed: + + bpy.ops.uv.select_all(action='DESELECT') + face_uvs[face][0].select = True; + bpy.ops.uv.select_linked()#Extend selection + + #Collect faces + faces_island = [face]; + for f in faces_unparsed: + if f != face and f.select and f.loops[0][uv_layers].select: + print("append "+str(f.index)) + faces_island.append(f) + for f in faces_island: + faces_unparsed.remove(f) + + #Assign Faces to island + faces_islands[face] = faces_island + + print("Sets: {}x".format(len(faces_islands))) + + # Align each island to its edges + for face in faces_islands: + align_island(face_uvs[face][0].uv, face_uvs[face][1].uv, faces_islands[face]) def align_island(uv_vert0, uv_vert1, faces): - bm = bmesh.from_edit_mesh(bpy.context.active_object.data) - uv_layers = bm.loops.layers.uv.verify() + bm = bmesh.from_edit_mesh(bpy.context.active_object.data) + uv_layers = bm.loops.layers.uv.verify() - print("Align {}x faces".format(len(faces))) + print("Align {}x faces".format(len(faces))) - # Select faces - bpy.ops.uv.select_all(action='DESELECT') - for face in faces: - for loop in face.loops: - loop[uv_layers].select = True + # Select faces + bpy.ops.uv.select_all(action='DESELECT') + for face in faces: + for loop in face.loops: + loop[uv_layers].select = True - diff = uv_vert1 - uv_vert0 - angle = math.atan2(diff.y, diff.x)%(math.pi/2) + diff = uv_vert1 - uv_vert0 + angle = math.atan2(diff.y, diff.x)%(math.pi/2) - bpy.ops.uv.select_linked() + bpy.ops.uv.select_linked() - bpy.context.tool_settings.transform_pivot_point = 'CURSOR' - bpy.ops.uv.cursor_set(location=uv_vert0 + diff/2) + bpy.context.tool_settings.transform_pivot_point = 'CURSOR' + bpy.ops.uv.cursor_set(location=uv_vert0 + diff/2) - if angle >= (math.pi/4): - angle = angle - (math.pi/2) + if angle >= (math.pi/4): + angle = angle - (math.pi/2) - bpy.ops.transform.rotate(value=angle, orient_axis='Z', constraint_axis=(False, False, False), orient_type='GLOBAL', mirror=False, use_proportional_edit=False) + bpy.ops.transform.rotate(value=angle, orient_axis='Z', constraint_axis=(False, False, False), orient_type='GLOBAL', mirror=False, use_proportional_edit=False) bpy.utils.register_class(op) diff --git a/op_island_align_sort.py b/op_island_align_sort.py index 6b9df02..70100f9 100644 --- a/op_island_align_sort.py +++ b/op_island_align_sort.py @@ -13,159 +13,159 @@ imp.reload(utilities_uv) class op(bpy.types.Operator): - bl_idname = "uv.textools_island_align_sort" - bl_label = "Align & Sort" - bl_description = "Rotates UV islands to minimal bounds and sorts them horizontal or vertical" - bl_options = {'REGISTER', 'UNDO'} + bl_idname = "uv.textools_island_align_sort" + bl_label = "Align & Sort" + bl_description = "Rotates UV islands to minimal bounds and sorts them horizontal or vertical" + bl_options = {'REGISTER', 'UNDO'} - is_vertical : bpy.props.BoolProperty(description="Vertical or Horizontal orientation", default=True) - padding : bpy.props.FloatProperty(description="Padding between UV islands", default=0.05) + is_vertical : bpy.props.BoolProperty(description="Vertical or Horizontal orientation", default=True) + padding : bpy.props.FloatProperty(description="Padding between UV islands", default=0.05) - @classmethod - def poll(cls, context): + @classmethod + def poll(cls, context): - if not bpy.context.active_object: - return False + if not bpy.context.active_object: + return False - if bpy.context.active_object.type != 'MESH': - return False + if bpy.context.active_object.type != 'MESH': + return False - #Only in Edit mode - if bpy.context.active_object.mode != 'EDIT': - return False + #Only in Edit mode + if bpy.context.active_object.mode != 'EDIT': + return False - #Only in UV editor mode - if bpy.context.area.type != 'IMAGE_EDITOR': - return False - - #Requires UV map - if not bpy.context.object.data.uv_layers: - return False #self.report({'WARNING'}, "Object must have more than one UV map") + #Only in UV editor mode + if bpy.context.area.type != 'IMAGE_EDITOR': + return False + + #Requires UV map + if not bpy.context.object.data.uv_layers: + return False #self.report({'WARNING'}, "Object must have more than one UV map") - #Not in Synced mode - if bpy.context.scene.tool_settings.use_uv_select_sync: - return False + #Not in Synced mode + if bpy.context.scene.tool_settings.use_uv_select_sync: + return False - return True + return True - def execute(self, context): - main(context, self.is_vertical, self.padding) - return {'FINISHED'} + def execute(self, context): + main(context, self.is_vertical, self.padding) + return {'FINISHED'} def main(context, isVertical, padding): - print("Executing IslandsAlignSort main {}".format(padding)) - - #Store selection - utilities_uv.selection_store() + print("Executing IslandsAlignSort main {}".format(padding)) + + #Store selection + utilities_uv.selection_store() - if bpy.context.tool_settings.transform_pivot_point != 'CURSOR': - bpy.context.tool_settings.transform_pivot_point = 'CURSOR' + if bpy.context.tool_settings.transform_pivot_point != 'CURSOR': + bpy.context.tool_settings.transform_pivot_point = 'CURSOR' - #Only in Face or Island mode - if bpy.context.scene.tool_settings.uv_select_mode is not 'FACE' or 'ISLAND': - bpy.context.scene.tool_settings.uv_select_mode = 'FACE' + #Only in Face or Island mode + if bpy.context.scene.tool_settings.uv_select_mode is not 'FACE' or 'ISLAND': + bpy.context.scene.tool_settings.uv_select_mode = 'FACE' - bm = bmesh.from_edit_mesh(bpy.context.active_object.data); - uv_layers = bm.loops.layers.uv.verify(); - + bm = bmesh.from_edit_mesh(bpy.context.active_object.data); + uv_layers = bm.loops.layers.uv.verify(); + - boundsAll = utilities_uv.getSelectionBBox() + boundsAll = utilities_uv.getSelectionBBox() - islands = utilities_uv.getSelectionIslands() - allSizes = {} #https://stackoverflow.com/questions/613183/sort-a-python-dictionary-by-value - allBounds = {} + islands = utilities_uv.getSelectionIslands() + allSizes = {} #https://stackoverflow.com/questions/613183/sort-a-python-dictionary-by-value + allBounds = {} - print("Islands: "+str(len(islands))+"x") + print("Islands: "+str(len(islands))+"x") - bpy.context.window_manager.progress_begin(0, len(islands)) + bpy.context.window_manager.progress_begin(0, len(islands)) - #Rotate to minimal bounds - for i in range(0, len(islands)): - alignIslandMinimalBounds(uv_layers, islands[i]) + #Rotate to minimal bounds + for i in range(0, len(islands)): + alignIslandMinimalBounds(uv_layers, islands[i]) - # Collect BBox sizes - bounds = utilities_uv.getSelectionBBox() - allSizes[i] = max(bounds['width'], bounds['height']) + i*0.000001;#Make each size unique - allBounds[i] = bounds; - print("Rotate compact: "+str(allSizes[i])) + # Collect BBox sizes + bounds = utilities_uv.getSelectionBBox() + allSizes[i] = max(bounds['width'], bounds['height']) + i*0.000001;#Make each size unique + allBounds[i] = bounds; + print("Rotate compact: "+str(allSizes[i])) - bpy.context.window_manager.progress_update(i) + bpy.context.window_manager.progress_update(i) - bpy.context.window_manager.progress_end() + bpy.context.window_manager.progress_end() - #Position by sorted size in row - sortedSizes = sorted(allSizes.items(), key=operator.itemgetter(1))#Sort by values, store tuples - sortedSizes.reverse() - offset = 0.0 - for sortedSize in sortedSizes: - index = sortedSize[0] - island = islands[index] - bounds = allBounds[index] + #Position by sorted size in row + sortedSizes = sorted(allSizes.items(), key=operator.itemgetter(1))#Sort by values, store tuples + sortedSizes.reverse() + offset = 0.0 + for sortedSize in sortedSizes: + index = sortedSize[0] + island = islands[index] + bounds = allBounds[index] - #Select Island - bpy.ops.uv.select_all(action='DESELECT') - utilities_uv.set_selected_faces(island) - - #Offset Island - if(isVertical): - delta = Vector((boundsAll['min'].x - bounds['min'].x, boundsAll['max'].y - bounds['max'].y)); - bpy.ops.transform.translate(value=(delta.x, delta.y-offset, 0)) - offset += bounds['height']+padding - else: - print("Horizontal") - delta = Vector((boundsAll['min'].x - bounds['min'].x, boundsAll['max'].y - bounds['max'].y)); - bpy.ops.transform.translate(value=(delta.x+offset, delta.y, 0)) - offset += bounds['width']+padding + #Select Island + bpy.ops.uv.select_all(action='DESELECT') + utilities_uv.set_selected_faces(island) + + #Offset Island + if(isVertical): + delta = Vector((boundsAll['min'].x - bounds['min'].x, boundsAll['max'].y - bounds['max'].y)); + bpy.ops.transform.translate(value=(delta.x, delta.y-offset, 0)) + offset += bounds['height']+padding + else: + print("Horizontal") + delta = Vector((boundsAll['min'].x - bounds['min'].x, boundsAll['max'].y - bounds['max'].y)); + bpy.ops.transform.translate(value=(delta.x+offset, delta.y, 0)) + offset += bounds['width']+padding - #Restore selection - utilities_uv.selection_restore() + #Restore selection + utilities_uv.selection_restore() def alignIslandMinimalBounds(uv_layers, faces): - # Select Island - bpy.ops.uv.select_all(action='DESELECT') - utilities_uv.set_selected_faces(faces) - - steps = 8 - angle = 45; # Starting Angle, half each step - - bboxPrevious = utilities_uv.getSelectionBBox() - - for i in range(0, steps): - # Rotate right - bpy.ops.transform.rotate(value=(angle * math.pi / 180), orient_axis='Z') - bbox = utilities_uv.getSelectionBBox() - - if i == 0: - sizeA = bboxPrevious['width'] * bboxPrevious['height'] - sizeB = bbox['width'] * bbox['height'] - if abs(bbox['width'] - bbox['height']) <= 0.0001 and sizeA < sizeB: - # print("Already squared") - bpy.ops.transform.rotate(value=(-angle * math.pi / 180), orient_axis='Z') - break; - - - if bbox['minLength'] < bboxPrevious['minLength']: - bboxPrevious = bbox; # Success - else: - # Rotate Left - bpy.ops.transform.rotate(value=(-angle*2 * math.pi / 180), orient_axis='Z') - bbox = utilities_uv.getSelectionBBox() - if bbox['minLength'] < bboxPrevious['minLength']: - bboxPrevious = bbox; # Success - else: - # Restore angle of this iteration - bpy.ops.transform.rotate(value=(angle * math.pi / 180), orient_axis='Z') - - angle = angle / 2 - - if bboxPrevious['width'] < bboxPrevious['height']: - bpy.ops.transform.rotate(value=(90 * math.pi / 180), orient_axis='Z') + # Select Island + bpy.ops.uv.select_all(action='DESELECT') + utilities_uv.set_selected_faces(faces) + + steps = 8 + angle = 45; # Starting Angle, half each step + + bboxPrevious = utilities_uv.getSelectionBBox() + + for i in range(0, steps): + # Rotate right + bpy.ops.transform.rotate(value=(angle * math.pi / 180), orient_axis='Z') + bbox = utilities_uv.getSelectionBBox() + + if i == 0: + sizeA = bboxPrevious['width'] * bboxPrevious['height'] + sizeB = bbox['width'] * bbox['height'] + if abs(bbox['width'] - bbox['height']) <= 0.0001 and sizeA < sizeB: + # print("Already squared") + bpy.ops.transform.rotate(value=(-angle * math.pi / 180), orient_axis='Z') + break; + + + if bbox['minLength'] < bboxPrevious['minLength']: + bboxPrevious = bbox; # Success + else: + # Rotate Left + bpy.ops.transform.rotate(value=(-angle*2 * math.pi / 180), orient_axis='Z') + bbox = utilities_uv.getSelectionBBox() + if bbox['minLength'] < bboxPrevious['minLength']: + bboxPrevious = bbox; # Success + else: + # Restore angle of this iteration + bpy.ops.transform.rotate(value=(angle * math.pi / 180), orient_axis='Z') + + angle = angle / 2 + + if bboxPrevious['width'] < bboxPrevious['height']: + bpy.ops.transform.rotate(value=(90 * math.pi / 180), orient_axis='Z') bpy.utils.register_class(op) \ No newline at end of file diff --git a/op_island_align_world.py b/op_island_align_world.py index a552d4d..8333602 100644 --- a/op_island_align_world.py +++ b/op_island_align_world.py @@ -14,278 +14,278 @@ from . import utilities_uv class op(bpy.types.Operator): - bl_idname = "uv.textools_island_align_world" - bl_label = "Align World" - bl_description = "Align selected UV islands to world / gravity directions" - bl_options = {'REGISTER', 'UNDO'} + bl_idname = "uv.textools_island_align_world" + bl_label = "Align World" + bl_description = "Align selected UV islands to world / gravity directions" + bl_options = {'REGISTER', 'UNDO'} - # is_global = bpy.props.BoolProperty( - # name = "Global Axis", - # description = "Global or local object axis alignment", - # default = False - # ) + # is_global = bpy.props.BoolProperty( + # name = "Global Axis", + # description = "Global or local object axis alignment", + # default = False + # ) - # def draw(self, context): - # layout = self.layout - # layout.prop(self, "is_global") + # def draw(self, context): + # layout = self.layout + # layout.prop(self, "is_global") - @classmethod - def poll(cls, context): - if not bpy.context.active_object: - return False + @classmethod + def poll(cls, context): + if not bpy.context.active_object: + return False - #Only in Edit mode - if bpy.context.active_object.mode != 'EDIT': - return False + #Only in Edit mode + if bpy.context.active_object.mode != 'EDIT': + return False - #Requires UV map - if not bpy.context.object.data.uv_layers: - return False + #Requires UV map + if not bpy.context.object.data.uv_layers: + return False - if bpy.context.scene.tool_settings.use_uv_select_sync: - return False + if bpy.context.scene.tool_settings.use_uv_select_sync: + return False - #Only in UV editor mode - if bpy.context.area.type != 'IMAGE_EDITOR': - return False + #Only in UV editor mode + if bpy.context.area.type != 'IMAGE_EDITOR': + return False - return True + return True - def execute(self, context): - main(self) - return {'FINISHED'} + def execute(self, context): + main(self) + return {'FINISHED'} def main(context): - print("\n________________________\nis_global") + print("\n________________________\nis_global") - #Store selection - utilities_uv.selection_store() + #Store selection + utilities_uv.selection_store() - bm = bmesh.from_edit_mesh(bpy.context.active_object.data) - uv_layers = bm.loops.layers.uv.verify() + bm = bmesh.from_edit_mesh(bpy.context.active_object.data) + uv_layers = bm.loops.layers.uv.verify() - #Only in Face or Island mode - if bpy.context.scene.tool_settings.uv_select_mode is not 'FACE' or 'ISLAND': - bpy.context.scene.tool_settings.uv_select_mode = 'FACE' + #Only in Face or Island mode + if bpy.context.scene.tool_settings.uv_select_mode is not 'FACE' or 'ISLAND': + bpy.context.scene.tool_settings.uv_select_mode = 'FACE' - obj = bpy.context.object - bm = bmesh.from_edit_mesh(bpy.context.active_object.data); - uv_layers = bm.loops.layers.uv.verify(); - - islands = utilities_uv.getSelectionIslands() + obj = bpy.context.object + bm = bmesh.from_edit_mesh(bpy.context.active_object.data); + uv_layers = bm.loops.layers.uv.verify(); + + islands = utilities_uv.getSelectionIslands() - + - for faces in islands: - # Get average viewport normal of UV island - avg_normal = Vector((0,0,0)) - for face in faces: - avg_normal+=face.normal - avg_normal/=len(faces) + for faces in islands: + # Get average viewport normal of UV island + avg_normal = Vector((0,0,0)) + for face in faces: + avg_normal+=face.normal + avg_normal/=len(faces) - # avg_normal = (obj.matrix_world*avg_normal).normalized() + # avg_normal = (obj.matrix_world*avg_normal).normalized() - # Which Side - x = 0 - y = 1 - z = 2 - max_size = max(abs(avg_normal.x), abs(avg_normal.y), abs(avg_normal.z)) - - # Use multiple steps - for i in range(3): - if(abs(avg_normal.x) == max_size): - print("x normal") - align_island(obj, bm, uv_layers, faces, y, z, avg_normal.x < 0, False) + # Which Side + x = 0 + y = 1 + z = 2 + max_size = max(abs(avg_normal.x), abs(avg_normal.y), abs(avg_normal.z)) + + # Use multiple steps + for i in range(3): + if(abs(avg_normal.x) == max_size): + print("x normal") + align_island(obj, bm, uv_layers, faces, y, z, avg_normal.x < 0, False) - elif(abs(avg_normal.y) == max_size): - print("y normal") - align_island(obj, bm, uv_layers, faces, x, z, avg_normal.y > 0, False) + elif(abs(avg_normal.y) == max_size): + print("y normal") + align_island(obj, bm, uv_layers, faces, x, z, avg_normal.y > 0, False) - elif(abs(avg_normal.z) == max_size): - print("z normal") - align_island(obj, bm, uv_layers, faces, x, y, False, avg_normal.z < 0) + elif(abs(avg_normal.z) == max_size): + print("z normal") + align_island(obj, bm, uv_layers, faces, x, y, False, avg_normal.z < 0) - print("align island: faces {}x n:{}, max:{}".format(len(faces), avg_normal, max_size)) + print("align island: faces {}x n:{}, max:{}".format(len(faces), avg_normal, max_size)) - + - #Restore selection - utilities_uv.selection_restore() + #Restore selection + utilities_uv.selection_restore() def align_island(obj, bm, uv_layers, faces, x=0, y=1, flip_x=False, flip_y=False): - # Find lowest and highest verts - minmax_val = [0,0] - minmax_vert = [None, None] - - axis_names = ['x', 'y', 'z'] - print("Align shell {}x at {},{} flip {},{}".format(len(faces), axis_names[x], axis_names[y], flip_x, flip_y)) - - - # print(" Min #{} , Max #{} along '{}'".format(minmax_vert[0].index, minmax_vert[1].index, axis_names[y] )) - # print(" A1 {:.1f} , A2 {:.1f} along ".format(minmax_val[0], minmax_val[1] )) - + # Find lowest and highest verts + minmax_val = [0,0] + minmax_vert = [None, None] + + axis_names = ['x', 'y', 'z'] + print("Align shell {}x at {},{} flip {},{}".format(len(faces), axis_names[x], axis_names[y], flip_x, flip_y)) + + + # print(" Min #{} , Max #{} along '{}'".format(minmax_vert[0].index, minmax_vert[1].index, axis_names[y] )) + # print(" A1 {:.1f} , A2 {:.1f} along ".format(minmax_val[0], minmax_val[1] )) + - # Collect UV to Vert - vert_to_uv = utilities_uv.get_vert_to_uv(bm, uv_layers) - uv_to_vert = utilities_uv.get_uv_to_vert(bm, uv_layers) - - processed_edges = [] - edges = [] - for face in faces: - for edge in face.edges: - if edge not in processed_edges: - processed_edges.append(edge) - delta = edge.verts[0].co -edge.verts[1].co - max_side = max(abs(delta.x), abs(delta.y), abs(delta.z)) - - # Check edges dominant in active axis - if( abs(delta[x]) == max_side or abs(delta[y]) == max_side): - # if( abs(delta[y]) == max_side): - edges.append(edge) - - print("Edges {}x".format(len(edges))) - - avg_angle = 0 - for edge in edges: - uv0 = vert_to_uv[ edge.verts[0] ][0] - uv1 = vert_to_uv[ edge.verts[1] ][0] - delta_verts = Vector(( - edge.verts[1].co[x] - edge.verts[0].co[x], - edge.verts[1].co[y] - edge.verts[0].co[y] - )) - - - if flip_x: - delta_verts.x = -edge.verts[1].co[x] + edge.verts[0].co[x] - if flip_y: - delta_verts.y = -edge.verts[1].co[y] + edge.verts[0].co[y] - - # delta_verts.y = edge.verts[0].co[y] - edge.verts[1].co[y] - - - delta_uvs = Vector(( - uv1.uv.x - uv0.uv.x, - uv1.uv.y - uv0.uv.y - )) - a0 = math.atan2(delta_verts.y, delta_verts.x) - math.pi/2 - a1 = math.atan2(delta_uvs.y, delta_uvs.x) - math.pi/2 - - - - - a_delta = math.atan2(math.sin(a0-a1), math.cos(a0-a1)) - # edge.verts[0].index, edge.verts[1].index - # print(" turn {:.1f} .. {:.1f} , {:.1f}".format(a_delta*180/math.pi, a0*180/math.pi,a1*180/math.pi)) - avg_angle+=a_delta - - - avg_angle/=len(edges) # - math.pi/2 - print("Turn {:.1f}".format(avg_angle * 180/math.pi)) - - bpy.ops.uv.select_all(action='DESELECT') - for face in faces: - for loop in face.loops: - loop[uv_layers].select = True - - - bpy.context.tool_settings.transform_pivot_point = 'MEDIAN_POINT' - bpy.ops.transform.rotate(value=avg_angle, orient_axis='Z') - # bpy.ops.transform.rotate(value=0.58191, axis=(-0, -0, -1), constraint_axis=(False, False, False), orient_type='GLOBAL', mirror=False, use_proportional_edit=False, proportional_edit_falloff='SPHERE', proportional_size=0.0267348) - - - # processed = [] - - - ''' - bpy.ops.uv.select_all(action='DESELECT') - for face in faces: - - # Collect UV to Vert - for loop in face.loops: - loop[uv_layers].select = True - vert = loop.vert - uv = loop[uv_layers] - # vert_to_uv - if vert not in vert_to_uv: - vert_to_uv[vert] = [uv]; - else: - vert_to_uv[vert].append(uv) - # uv_to_vert - if uv not in uv_to_vert: - uv_to_vert[ uv ] = vert; - - - for vert in face.verts: - if vert not in processed: - processed.append(vert) - - vert_y = (vert.co)[y] #obj.matrix_world * - - print("idx {} = {}".format(vert.index, vert_y)) - - if not minmax_vert[0] or not minmax_vert[1]: - minmax_vert[0] = vert - minmax_vert[1] = vert - minmax_val[0] = vert_y - minmax_val[1] = vert_y - continue - - if vert_y < minmax_val[0]: - # Not yet defined or smaller - minmax_vert[0] = vert - minmax_val[0] = vert_y - - elif vert_y > minmax_val[1]: - minmax_vert[1] = vert - minmax_val[1] = vert_y - - - if minmax_vert[0] and minmax_vert[1]: - axis_names = ['x', 'y', 'z'] - print(" Min #{} , Max #{} along '{}'".format(minmax_vert[0].index, minmax_vert[1].index, axis_names[y] )) - # print(" A1 {:.1f} , A2 {:.1f} along ".format(minmax_val[0], minmax_val[1] )) - - vert_A = minmax_vert[0] - vert_B = minmax_vert[1] - uv_A = vert_to_uv[vert_A][0] - uv_B = vert_to_uv[vert_B][0] - - delta_verts = Vector(( - vert_B.co[x] - vert_A.co[x], - vert_B.co[y] - vert_A.co[y] - )) - - delta_uvs = Vector(( - uv_B.uv.x - uv_A.uv.x, - uv_B.uv.y - uv_A.uv.y, - - )) - # Get angles - angle_vert = math.atan2(delta_verts.y, delta_verts.x) - math.pi/2 - angle_uv = math.atan2(delta_uvs.y, delta_uvs.x) - math.pi/2 - - angle_delta = math.atan2(math.sin(angle_vert-angle_uv), math.cos(angle_vert-angle_uv)) - - print(" Angles {:.2f} | {:.2f}".format(angle_vert*180/math.pi, angle_uv*180/math.pi)) - print(" Angle Diff {:.2f}".format(angle_delta*180/math.pi)) - - bpy.context.tool_settings.transform_pivot_point = 'MEDIAN_POINT' - bpy.ops.transform.rotate(value=angle_delta, axis='Z') - # bpy.ops.transform.rotate(value=0.58191, axis=(-0, -0, -1), constraint_axis=(False, False, False), orient_type='GLOBAL', mirror=False, use_proportional_edit=False, proportional_edit_falloff='SPHERE', proportional_size=0.0267348) - - - # bpy.ops.mesh.select_all(action='DESELECT') - # vert_A.select = True - # vert_B.select = True - - # return - ''' + # Collect UV to Vert + vert_to_uv = utilities_uv.get_vert_to_uv(bm, uv_layers) + uv_to_vert = utilities_uv.get_uv_to_vert(bm, uv_layers) + + processed_edges = [] + edges = [] + for face in faces: + for edge in face.edges: + if edge not in processed_edges: + processed_edges.append(edge) + delta = edge.verts[0].co -edge.verts[1].co + max_side = max(abs(delta.x), abs(delta.y), abs(delta.z)) + + # Check edges dominant in active axis + if( abs(delta[x]) == max_side or abs(delta[y]) == max_side): + # if( abs(delta[y]) == max_side): + edges.append(edge) + + print("Edges {}x".format(len(edges))) + + avg_angle = 0 + for edge in edges: + uv0 = vert_to_uv[ edge.verts[0] ][0] + uv1 = vert_to_uv[ edge.verts[1] ][0] + delta_verts = Vector(( + edge.verts[1].co[x] - edge.verts[0].co[x], + edge.verts[1].co[y] - edge.verts[0].co[y] + )) + + + if flip_x: + delta_verts.x = -edge.verts[1].co[x] + edge.verts[0].co[x] + if flip_y: + delta_verts.y = -edge.verts[1].co[y] + edge.verts[0].co[y] + + # delta_verts.y = edge.verts[0].co[y] - edge.verts[1].co[y] + + + delta_uvs = Vector(( + uv1.uv.x - uv0.uv.x, + uv1.uv.y - uv0.uv.y + )) + a0 = math.atan2(delta_verts.y, delta_verts.x) - math.pi/2 + a1 = math.atan2(delta_uvs.y, delta_uvs.x) - math.pi/2 + + + + + a_delta = math.atan2(math.sin(a0-a1), math.cos(a0-a1)) + # edge.verts[0].index, edge.verts[1].index + # print(" turn {:.1f} .. {:.1f} , {:.1f}".format(a_delta*180/math.pi, a0*180/math.pi,a1*180/math.pi)) + avg_angle+=a_delta + + + avg_angle/=len(edges) # - math.pi/2 + print("Turn {:.1f}".format(avg_angle * 180/math.pi)) + + bpy.ops.uv.select_all(action='DESELECT') + for face in faces: + for loop in face.loops: + loop[uv_layers].select = True + + + bpy.context.tool_settings.transform_pivot_point = 'MEDIAN_POINT' + bpy.ops.transform.rotate(value=avg_angle, orient_axis='Z') + # bpy.ops.transform.rotate(value=0.58191, axis=(-0, -0, -1), constraint_axis=(False, False, False), orient_type='GLOBAL', mirror=False, use_proportional_edit=False, proportional_edit_falloff='SPHERE', proportional_size=0.0267348) + + + # processed = [] + + + ''' + bpy.ops.uv.select_all(action='DESELECT') + for face in faces: + + # Collect UV to Vert + for loop in face.loops: + loop[uv_layers].select = True + vert = loop.vert + uv = loop[uv_layers] + # vert_to_uv + if vert not in vert_to_uv: + vert_to_uv[vert] = [uv]; + else: + vert_to_uv[vert].append(uv) + # uv_to_vert + if uv not in uv_to_vert: + uv_to_vert[ uv ] = vert; + + + for vert in face.verts: + if vert not in processed: + processed.append(vert) + + vert_y = (vert.co)[y] #obj.matrix_world * + + print("idx {} = {}".format(vert.index, vert_y)) + + if not minmax_vert[0] or not minmax_vert[1]: + minmax_vert[0] = vert + minmax_vert[1] = vert + minmax_val[0] = vert_y + minmax_val[1] = vert_y + continue + + if vert_y < minmax_val[0]: + # Not yet defined or smaller + minmax_vert[0] = vert + minmax_val[0] = vert_y + + elif vert_y > minmax_val[1]: + minmax_vert[1] = vert + minmax_val[1] = vert_y + + + if minmax_vert[0] and minmax_vert[1]: + axis_names = ['x', 'y', 'z'] + print(" Min #{} , Max #{} along '{}'".format(minmax_vert[0].index, minmax_vert[1].index, axis_names[y] )) + # print(" A1 {:.1f} , A2 {:.1f} along ".format(minmax_val[0], minmax_val[1] )) + + vert_A = minmax_vert[0] + vert_B = minmax_vert[1] + uv_A = vert_to_uv[vert_A][0] + uv_B = vert_to_uv[vert_B][0] + + delta_verts = Vector(( + vert_B.co[x] - vert_A.co[x], + vert_B.co[y] - vert_A.co[y] + )) + + delta_uvs = Vector(( + uv_B.uv.x - uv_A.uv.x, + uv_B.uv.y - uv_A.uv.y, + + )) + # Get angles + angle_vert = math.atan2(delta_verts.y, delta_verts.x) - math.pi/2 + angle_uv = math.atan2(delta_uvs.y, delta_uvs.x) - math.pi/2 + + angle_delta = math.atan2(math.sin(angle_vert-angle_uv), math.cos(angle_vert-angle_uv)) + + print(" Angles {:.2f} | {:.2f}".format(angle_vert*180/math.pi, angle_uv*180/math.pi)) + print(" Angle Diff {:.2f}".format(angle_delta*180/math.pi)) + + bpy.context.tool_settings.transform_pivot_point = 'MEDIAN_POINT' + bpy.ops.transform.rotate(value=angle_delta, axis='Z') + # bpy.ops.transform.rotate(value=0.58191, axis=(-0, -0, -1), constraint_axis=(False, False, False), orient_type='GLOBAL', mirror=False, use_proportional_edit=False, proportional_edit_falloff='SPHERE', proportional_size=0.0267348) + + + # bpy.ops.mesh.select_all(action='DESELECT') + # vert_A.select = True + # vert_B.select = True + + # return + ''' bpy.utils.register_class(op) diff --git a/op_island_mirror.py b/op_island_mirror.py index 5538afd..1282b2d 100644 --- a/op_island_mirror.py +++ b/op_island_mirror.py @@ -12,275 +12,275 @@ from . import utilities_uv class op(bpy.types.Operator): - bl_idname = "uv.textools_island_mirror" - bl_label = "Symmetry" - bl_description = "Mirrors selected faces to other half or averages based on selected edge center" - bl_options = {'REGISTER', 'UNDO'} - - is_stack : bpy.props.BoolProperty(description="Stack the halves on top of each other?", default=False) + bl_idname = "uv.textools_island_mirror" + bl_label = "Symmetry" + bl_description = "Mirrors selected faces to other half or averages based on selected edge center" + bl_options = {'REGISTER', 'UNDO'} + + is_stack : bpy.props.BoolProperty(description="Stack the halves on top of each other?", default=False) - @classmethod - def poll(cls, context): + @classmethod + def poll(cls, context): - if not bpy.context.active_object: - return False + if not bpy.context.active_object: + return False - #Only in Edit mode - if bpy.context.active_object.mode != 'EDIT': - return False + #Only in Edit mode + if bpy.context.active_object.mode != 'EDIT': + return False - #Only in UV editor mode - if bpy.context.area.type != 'IMAGE_EDITOR': - return False + #Only in UV editor mode + if bpy.context.area.type != 'IMAGE_EDITOR': + return False - #Requires UV map - if not bpy.context.object.data.uv_layers: - return False + #Requires UV map + if not bpy.context.object.data.uv_layers: + return False - if bpy.context.scene.tool_settings.use_uv_select_sync: - return False + if bpy.context.scene.tool_settings.use_uv_select_sync: + return False - if bpy.context.scene.tool_settings.uv_select_mode != 'EDGE' and bpy.context.scene.tool_settings.uv_select_mode != 'FACE': - return False + if bpy.context.scene.tool_settings.uv_select_mode != 'EDGE' and bpy.context.scene.tool_settings.uv_select_mode != 'FACE': + return False - # if bpy.context.scene.tool_settings.use_uv_select_sync: - # return False + # if bpy.context.scene.tool_settings.use_uv_select_sync: + # return False - return True + return True - def execute(self, context): - main(context) - return {'FINISHED'} + def execute(self, context): + main(context) + return {'FINISHED'} def main(context): - print("--------------------------- Executing operator_mirror") - - #Store selection - utilities_uv.selection_store() - - bm = bmesh.from_edit_mesh(bpy.context.active_object.data) - uv_layers = bm.loops.layers.uv.verify() - - if bpy.context.scene.tool_settings.uv_select_mode == 'EDGE': - - - # 1.) Collect left and right side verts - verts_middle = []; - - for face in bm.faces: - if face.select: - for loop in face.loops: - if loop[uv_layers].select and loop.vert not in verts_middle: - verts_middle.append(loop.vert) - - # 2.) Align UV shell - alignToCenterLine() - - # Convert to Vert selection and extend edge loop in 3D space - bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT') - bpy.ops.mesh.select_all(action='DESELECT') - for vert in verts_middle: - vert.select = True - - bpy.ops.mesh.select_mode(use_extend=True, use_expand=False, type='EDGE') - bpy.ops.mesh.loop_multi_select(ring=False) - for vert in bm.verts: - if vert.select and vert not in verts_middle: - print("Append extra vert to symmetry line from xyz edge loop") - verts_middle.append(vert) - - # Select UV shell Again - bpy.ops.mesh.select_linked(delimit={'UV'}) - verts_island = [] - for vert in bm.verts: - if vert.select: - verts_island.append(vert) - - - # 3.) Restore UV vert selection - x_middle = 0 - bpy.ops.uv.select_all(action='DESELECT') - for face in bm.faces: - if face.select: - for loop in face.loops: - if loop.vert in verts_middle: - loop[uv_layers].select = True - x_middle = loop[uv_layers].uv.x; - - - print("Middle "+str(len(verts_middle))+"x, x pos: "+str(x_middle)) - - # Extend selection - bpy.ops.uv.select_more() - verts_A = []; - verts_B = []; - for face in bm.faces: - if face.select: - for loop in face.loops: - if loop[uv_layers].select and loop.vert not in verts_middle: - if loop[uv_layers].uv.x <= x_middle: - # Left - if loop.vert not in verts_A: - verts_A.append(loop.vert) - - elif loop[uv_layers].uv.x > x_middle: - # Right - if loop.vert not in verts_B: - verts_B.append(loop.vert) - - - - - def remove_doubles(): - verts_double = [vert for vert in verts_island if (vert in verts_A and vert in verts_B)] - - # print("Temp double: "+str(len(verts_double))+"x") - if len(verts_double) > 0: - print("TODO: Remove doubles "+str(len(verts_double))) - for vert in verts_double: - verts_A.remove(vert) - verts_B.remove(vert) - verts_middle.append(vert) - - def extend_half_selection(verts_middle, verts_half, verts_other): - # Select initial half selection - bpy.ops.uv.select_all(action='DESELECT') - for face in bm.faces: - if face.select: - for loop in face.loops: - if loop.vert in verts_half: - loop[uv_layers].select = True - - # Extend selection - bpy.ops.uv.select_more() - - # count_added = 0 - for face in bm.faces: - if face.select: - for loop in face.loops: - if loop.vert not in verts_half and loop.vert not in verts_middle and loop[uv_layers].select: - verts_half.append(loop.vert) - - - remove_doubles() - - # Limit iteration loops - max_loops_extend = 200 - for i in range(0, max_loops_extend): - print("Now extend selection A / B") - count_hash = str(len(verts_A))+"_"+str(len(verts_B)); - extend_half_selection(verts_middle, verts_A, verts_B) - extend_half_selection(verts_middle, verts_B, verts_A) - remove_doubles() - - count_hash_new = str(len(verts_A))+"_"+str(len(verts_B)); - if count_hash_new == count_hash: - print("Break loop, same as previous loop") - break; - - print("Edge, Sides: L:"+str(len(verts_A))+" | R:"+str(len(verts_B))) - - # 4.) Mirror Verts - mirror_verts(verts_middle, verts_A, verts_B, False) - - - if bpy.context.scene.tool_settings.uv_select_mode == 'FACE': - - # 1.) Get selected UV faces to vert faces - selected_faces = [] - for face in bm.faces: - if face.select: - # Are all UV faces selected? - countSelected = 0 - for loop in face.loops: - if loop[uv_layers].select: - countSelected+=1 - # print("Vert selected "+str(face.index)) - if countSelected == len(face.loops): - selected_faces.append(face) - - - # if bpy.context.scene.tool_settings.use_uv_select_sync == False: - - bpy.ops.uv.select_linked() - verts_all = [] - for face in bm.faces: - if face.select: - for loop in face.loops: - if(loop.vert not in verts_all): - verts_all.append(loop.vert) - - print("Verts shell: "+str(len(verts_all))) - - - bpy.ops.mesh.select_all(action='DESELECT') - for face in selected_faces: - face.select = True - - - # 2.) Select Vert shell's outer edges - bpy.ops.mesh.select_linked(delimit=set()) - bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE') - bpy.ops.mesh.region_to_loop() - edges_outer_shell = [e for e in bm.edges if e.select] - - # 3.) Select Half's outer edges - bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE') - bpy.ops.mesh.select_all(action='DESELECT') - for face in selected_faces: - face.select = True - bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE') - bpy.ops.mesh.region_to_loop() - edges_outer_selected = [e for e in bm.edges if e.select] - - # 4.) Mask edges exclusive to edges_outer_selected (symmetry line) - edges_middle = [item for item in edges_outer_selected if item not in edges_outer_shell] - - # 5.) Convert to UV selection - verts_middle = [] - for edge in edges_middle: - if edge.verts[0] not in verts_middle: - verts_middle.append(edge.verts[0]) - if edge.verts[1] not in verts_middle: - verts_middle.append(edge.verts[1]) + print("--------------------------- Executing operator_mirror") + + #Store selection + utilities_uv.selection_store() + + bm = bmesh.from_edit_mesh(bpy.context.active_object.data) + uv_layers = bm.loops.layers.uv.verify() + + if bpy.context.scene.tool_settings.uv_select_mode == 'EDGE': + + + # 1.) Collect left and right side verts + verts_middle = []; + + for face in bm.faces: + if face.select: + for loop in face.loops: + if loop[uv_layers].select and loop.vert not in verts_middle: + verts_middle.append(loop.vert) + + # 2.) Align UV shell + alignToCenterLine() + + # Convert to Vert selection and extend edge loop in 3D space + bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT') + bpy.ops.mesh.select_all(action='DESELECT') + for vert in verts_middle: + vert.select = True + + bpy.ops.mesh.select_mode(use_extend=True, use_expand=False, type='EDGE') + bpy.ops.mesh.loop_multi_select(ring=False) + for vert in bm.verts: + if vert.select and vert not in verts_middle: + print("Append extra vert to symmetry line from xyz edge loop") + verts_middle.append(vert) + + # Select UV shell Again + bpy.ops.mesh.select_linked(delimit={'UV'}) + verts_island = [] + for vert in bm.verts: + if vert.select: + verts_island.append(vert) + + + # 3.) Restore UV vert selection + x_middle = 0 + bpy.ops.uv.select_all(action='DESELECT') + for face in bm.faces: + if face.select: + for loop in face.loops: + if loop.vert in verts_middle: + loop[uv_layers].select = True + x_middle = loop[uv_layers].uv.x; + + + print("Middle "+str(len(verts_middle))+"x, x pos: "+str(x_middle)) + + # Extend selection + bpy.ops.uv.select_more() + verts_A = []; + verts_B = []; + for face in bm.faces: + if face.select: + for loop in face.loops: + if loop[uv_layers].select and loop.vert not in verts_middle: + if loop[uv_layers].uv.x <= x_middle: + # Left + if loop.vert not in verts_A: + verts_A.append(loop.vert) + + elif loop[uv_layers].uv.x > x_middle: + # Right + if loop.vert not in verts_B: + verts_B.append(loop.vert) + + + + + def remove_doubles(): + verts_double = [vert for vert in verts_island if (vert in verts_A and vert in verts_B)] + + # print("Temp double: "+str(len(verts_double))+"x") + if len(verts_double) > 0: + print("TODO: Remove doubles "+str(len(verts_double))) + for vert in verts_double: + verts_A.remove(vert) + verts_B.remove(vert) + verts_middle.append(vert) + + def extend_half_selection(verts_middle, verts_half, verts_other): + # Select initial half selection + bpy.ops.uv.select_all(action='DESELECT') + for face in bm.faces: + if face.select: + for loop in face.loops: + if loop.vert in verts_half: + loop[uv_layers].select = True + + # Extend selection + bpy.ops.uv.select_more() + + # count_added = 0 + for face in bm.faces: + if face.select: + for loop in face.loops: + if loop.vert not in verts_half and loop.vert not in verts_middle and loop[uv_layers].select: + verts_half.append(loop.vert) + + + remove_doubles() + + # Limit iteration loops + max_loops_extend = 200 + for i in range(0, max_loops_extend): + print("Now extend selection A / B") + count_hash = str(len(verts_A))+"_"+str(len(verts_B)); + extend_half_selection(verts_middle, verts_A, verts_B) + extend_half_selection(verts_middle, verts_B, verts_A) + remove_doubles() + + count_hash_new = str(len(verts_A))+"_"+str(len(verts_B)); + if count_hash_new == count_hash: + print("Break loop, same as previous loop") + break; + + print("Edge, Sides: L:"+str(len(verts_A))+" | R:"+str(len(verts_B))) + + # 4.) Mirror Verts + mirror_verts(verts_middle, verts_A, verts_B, False) + + + if bpy.context.scene.tool_settings.uv_select_mode == 'FACE': + + # 1.) Get selected UV faces to vert faces + selected_faces = [] + for face in bm.faces: + if face.select: + # Are all UV faces selected? + countSelected = 0 + for loop in face.loops: + if loop[uv_layers].select: + countSelected+=1 + # print("Vert selected "+str(face.index)) + if countSelected == len(face.loops): + selected_faces.append(face) + + + # if bpy.context.scene.tool_settings.use_uv_select_sync == False: + + bpy.ops.uv.select_linked() + verts_all = [] + for face in bm.faces: + if face.select: + for loop in face.loops: + if(loop.vert not in verts_all): + verts_all.append(loop.vert) + + print("Verts shell: "+str(len(verts_all))) + + + bpy.ops.mesh.select_all(action='DESELECT') + for face in selected_faces: + face.select = True + + + # 2.) Select Vert shell's outer edges + bpy.ops.mesh.select_linked(delimit=set()) + bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE') + bpy.ops.mesh.region_to_loop() + edges_outer_shell = [e for e in bm.edges if e.select] + + # 3.) Select Half's outer edges + bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE') + bpy.ops.mesh.select_all(action='DESELECT') + for face in selected_faces: + face.select = True + bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE') + bpy.ops.mesh.region_to_loop() + edges_outer_selected = [e for e in bm.edges if e.select] + + # 4.) Mask edges exclusive to edges_outer_selected (symmetry line) + edges_middle = [item for item in edges_outer_selected if item not in edges_outer_shell] + + # 5.) Convert to UV selection + verts_middle = [] + for edge in edges_middle: + if edge.verts[0] not in verts_middle: + verts_middle.append(edge.verts[0]) + if edge.verts[1] not in verts_middle: + verts_middle.append(edge.verts[1]) - #Select all Vert shell faces - bpy.ops.mesh.select_linked(delimit=set()) - #Select UV matching vert array - bpy.ops.uv.select_all(action='DESELECT') - for face in bm.faces: - if face.select: - for loop in face.loops: - if loop.vert in verts_middle: - loop[uv_layers].select = True + #Select all Vert shell faces + bpy.ops.mesh.select_linked(delimit=set()) + #Select UV matching vert array + bpy.ops.uv.select_all(action='DESELECT') + for face in bm.faces: + if face.select: + for loop in face.loops: + if loop.vert in verts_middle: + loop[uv_layers].select = True - # 5.) Align UV shell - alignToCenterLine() + # 5.) Align UV shell + alignToCenterLine() - # 7.) Collect left and right side verts - verts_A = []; - verts_B = []; + # 7.) Collect left and right side verts + verts_A = []; + verts_B = []; - bpy.ops.uv.select_all(action='DESELECT') - for face in selected_faces: - for loop in face.loops: - if loop.vert not in verts_middle and loop.vert not in verts_A: - verts_A.append(loop.vert) + bpy.ops.uv.select_all(action='DESELECT') + for face in selected_faces: + for loop in face.loops: + if loop.vert not in verts_middle and loop.vert not in verts_A: + verts_A.append(loop.vert) - for vert in verts_all: - if vert not in verts_middle and vert not in verts_A and vert not in verts_B: - verts_B.append(vert) + for vert in verts_all: + if vert not in verts_middle and vert not in verts_A and vert not in verts_B: + verts_B.append(vert) - # 8.) Mirror Verts - mirror_verts(verts_middle, verts_A, verts_B, True) + # 8.) Mirror Verts + mirror_verts(verts_middle, verts_A, verts_B, True) - #Restore selection - # utilities_uv.selection_restore() + #Restore selection + # utilities_uv.selection_restore() @@ -288,496 +288,496 @@ def main(context): def mirror_verts(verts_middle, verts_A, verts_B, isAToB): - print("--------------------------------\nMirror: C:"+str(len(verts_middle))+" ; verts: "+str(len(verts_A))+"|"+str(len(verts_B))+"x, A to B? "+str(isAToB)) - - - bm = bmesh.from_edit_mesh(bpy.context.active_object.data) - uv_layers = bm.loops.layers.uv.verify() - - - # Get verts_island - verts_island = [] - for vert in verts_middle: - verts_island.append(vert) - for vert in verts_A: - verts_island.append(vert) - for vert in verts_B: - verts_island.append(vert) - - # Select Island as Faces - bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT') - bpy.ops.mesh.select_all(action='DESELECT') - for vert in verts_island: - vert.select = True - bpy.ops.mesh.select_mode(use_extend=False, use_expand=True, type='FACE') - - # Collect Librarys of verts / UV - vert_to_uv = utilities_uv.get_vert_to_uv(bm, uv_layers) - uv_to_vert = utilities_uv.get_uv_to_vert(bm, uv_layers) - uv_to_face = {} - # UV clusters / groups (within 0.000001 distance) - clusters = [] - uv_to_clusters = {} - vert_to_clusters = {} - - for face in bm.faces: - if face.select: - for loop in face.loops: - vert = loop.vert - uv = loop[uv_layers] - - if uv not in uv_to_face: - uv_to_face[ uv ] = face; - - # clusters - isMerged = False - for cluster in clusters: - d = (uv.uv - cluster.uvs[0].uv).length - if d <= 0.0000001: - #Merge - cluster.append(uv) - uv_to_clusters[uv] = cluster - if vert not in vert_to_clusters: - vert_to_clusters[vert] = cluster - isMerged = True; - break; - if not isMerged: - #New Group - clusters.append( UVCluster(vert, [uv]) ) - uv_to_clusters[uv] = clusters[-1] - if vert not in vert_to_clusters: - vert_to_clusters[vert] = clusters[-1] - - # Get Center X - x_middle = vert_to_uv[ verts_middle[0] ][0].uv.x; - - - # 3.) Grow layer by layer - bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT') - bpy.context.scene.tool_settings.uv_select_mode = 'VERTEX' - - - clusters_processed = [] - - def select_extend_filter(clusters_border, clusters_mask): - # print("Extend A/B") - connected_clusters = [] - for cluster in clusters_border: - - # Select and Extend selection - bpy.ops.uv.select_all(action='DESELECT') - for uv in cluster.uvs: - uv.select = True - bpy.ops.uv.select_more() - - # Collect extended - uv_extended = [uv for clusterMask in clusters_mask for uv in clusterMask.uvs if (uv.select and clusterMask not in clusters_processed)] - clusters_extended = [] - for uv in uv_extended: - if uv_to_clusters[uv] not in clusters_extended: - clusters_extended.append( uv_to_clusters[uv] ) - - # Sort by distance - groups_distance = {} - for i in range(0, len(clusters_extended)): - sub_group = clusters_extended[i] - groups_distance[i] = (cluster.uvs[0].uv - sub_group.uvs[0].uv).length - - # Append to connected clusters - array = [] - for item in sorted(groups_distance.items(), key=operator.itemgetter(1)): - key = item[0] - clust = clusters_extended[key] - array.append( clust ) - if clust not in clusters_processed: - clusters_processed.append(clust) - - connected_clusters.append( array ) - - if cluster not in clusters_processed: - clusters_processed.append( cluster ) - - - bpy.ops.uv.select_all(action='DESELECT') - for uv in uv_extended: - uv.select = True - - - return connected_clusters - - - - - - - mask_A = [vert_to_clusters[vert] for vert in verts_A] - mask_B = [vert_to_clusters[vert] for vert in verts_B] - - border_A = list([vert_to_clusters[vert] for vert in verts_middle]) - border_B = list([vert_to_clusters[vert] for vert in verts_middle]) - - for step in range(0, 8): - - if len(border_A) == 0: - print("{}.: Finished scanning with no growth iterations".format(step)) - break; - if len(border_A) != len(border_B) or len(border_A) == 0: - print("Abort: non compatible border A/B: {}x {}x ".format(len(border_A), len(border_B))) - break; - - print("{}.: border {}x|{}x, processed: {}x".format(step, len(border_A), len(border_B), len(clusters_processed))) - - # Collect connected pairs for each side - connected_A = select_extend_filter(border_A, mask_A) - connected_B = select_extend_filter(border_B, mask_B) - - print(" Connected: {}x|{}x".format(len(connected_A), len(connected_B))) - - border_A.clear() - border_B.clear() - - # Traverse through pairs - for i in range(0, min(len(connected_A), len(connected_B)) ): - if len(connected_A[i]) == 0: - continue - if len(connected_A[i]) != len(connected_B[i]): - print(". Error: Inconsistent grow mappings from {} {}x | {}x".format(i, len(connected_A[i]), len(connected_B[i]) )) - continue - - indexA = [cluster.vertex.index for cluster in connected_A[i] ] - indexB = [cluster.vertex.index for cluster in connected_B[i] ] - indexA = str(indexA).replace("[","").replace("]","").replace(" ","") - indexB = str(indexB).replace("[","").replace("]","").replace(" ","") - print(". Map {}|{} = {}x|{}x".format(indexA, indexB, len(connected_A[i]), len(connected_B[i]) ) ) + print("--------------------------------\nMirror: C:"+str(len(verts_middle))+" ; verts: "+str(len(verts_A))+"|"+str(len(verts_B))+"x, A to B? "+str(isAToB)) + + + bm = bmesh.from_edit_mesh(bpy.context.active_object.data) + uv_layers = bm.loops.layers.uv.verify() + + + # Get verts_island + verts_island = [] + for vert in verts_middle: + verts_island.append(vert) + for vert in verts_A: + verts_island.append(vert) + for vert in verts_B: + verts_island.append(vert) + + # Select Island as Faces + bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT') + bpy.ops.mesh.select_all(action='DESELECT') + for vert in verts_island: + vert.select = True + bpy.ops.mesh.select_mode(use_extend=False, use_expand=True, type='FACE') + + # Collect Librarys of verts / UV + vert_to_uv = utilities_uv.get_vert_to_uv(bm, uv_layers) + uv_to_vert = utilities_uv.get_uv_to_vert(bm, uv_layers) + uv_to_face = {} + # UV clusters / groups (within 0.000001 distance) + clusters = [] + uv_to_clusters = {} + vert_to_clusters = {} + + for face in bm.faces: + if face.select: + for loop in face.loops: + vert = loop.vert + uv = loop[uv_layers] + + if uv not in uv_to_face: + uv_to_face[ uv ] = face; + + # clusters + isMerged = False + for cluster in clusters: + d = (uv.uv - cluster.uvs[0].uv).length + if d <= 0.0000001: + #Merge + cluster.append(uv) + uv_to_clusters[uv] = cluster + if vert not in vert_to_clusters: + vert_to_clusters[vert] = cluster + isMerged = True; + break; + if not isMerged: + #New Group + clusters.append( UVCluster(vert, [uv]) ) + uv_to_clusters[uv] = clusters[-1] + if vert not in vert_to_clusters: + vert_to_clusters[vert] = clusters[-1] + + # Get Center X + x_middle = vert_to_uv[ verts_middle[0] ][0].uv.x; + + + # 3.) Grow layer by layer + bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT') + bpy.context.scene.tool_settings.uv_select_mode = 'VERTEX' + + + clusters_processed = [] + + def select_extend_filter(clusters_border, clusters_mask): + # print("Extend A/B") + connected_clusters = [] + for cluster in clusters_border: + + # Select and Extend selection + bpy.ops.uv.select_all(action='DESELECT') + for uv in cluster.uvs: + uv.select = True + bpy.ops.uv.select_more() + + # Collect extended + uv_extended = [uv for clusterMask in clusters_mask for uv in clusterMask.uvs if (uv.select and clusterMask not in clusters_processed)] + clusters_extended = [] + for uv in uv_extended: + if uv_to_clusters[uv] not in clusters_extended: + clusters_extended.append( uv_to_clusters[uv] ) + + # Sort by distance + groups_distance = {} + for i in range(0, len(clusters_extended)): + sub_group = clusters_extended[i] + groups_distance[i] = (cluster.uvs[0].uv - sub_group.uvs[0].uv).length + + # Append to connected clusters + array = [] + for item in sorted(groups_distance.items(), key=operator.itemgetter(1)): + key = item[0] + clust = clusters_extended[key] + array.append( clust ) + if clust not in clusters_processed: + clusters_processed.append(clust) + + connected_clusters.append( array ) + + if cluster not in clusters_processed: + clusters_processed.append( cluster ) + + + bpy.ops.uv.select_all(action='DESELECT') + for uv in uv_extended: + uv.select = True + + + return connected_clusters + + + + + + + mask_A = [vert_to_clusters[vert] for vert in verts_A] + mask_B = [vert_to_clusters[vert] for vert in verts_B] + + border_A = list([vert_to_clusters[vert] for vert in verts_middle]) + border_B = list([vert_to_clusters[vert] for vert in verts_middle]) + + for step in range(0, 8): + + if len(border_A) == 0: + print("{}.: Finished scanning with no growth iterations".format(step)) + break; + if len(border_A) != len(border_B) or len(border_A) == 0: + print("Abort: non compatible border A/B: {}x {}x ".format(len(border_A), len(border_B))) + break; + + print("{}.: border {}x|{}x, processed: {}x".format(step, len(border_A), len(border_B), len(clusters_processed))) + + # Collect connected pairs for each side + connected_A = select_extend_filter(border_A, mask_A) + connected_B = select_extend_filter(border_B, mask_B) + + print(" Connected: {}x|{}x".format(len(connected_A), len(connected_B))) + + border_A.clear() + border_B.clear() + + # Traverse through pairs + for i in range(0, min(len(connected_A), len(connected_B)) ): + if len(connected_A[i]) == 0: + continue + if len(connected_A[i]) != len(connected_B[i]): + print(". Error: Inconsistent grow mappings from {} {}x | {}x".format(i, len(connected_A[i]), len(connected_B[i]) )) + continue + + indexA = [cluster.vertex.index for cluster in connected_A[i] ] + indexB = [cluster.vertex.index for cluster in connected_B[i] ] + indexA = str(indexA).replace("[","").replace("]","").replace(" ","") + indexB = str(indexB).replace("[","").replace("]","").replace(" ","") + print(". Map {}|{} = {}x|{}x".format(indexA, indexB, len(connected_A[i]), len(connected_B[i]) ) ) - if True:#isAToB: - # Copy A side to B - for cluster in connected_B[i]: - for uv in cluster.uvs: - pos = connected_A[i][0].uvs[0].uv.copy() - pos.x = x_middle - (pos.x-x_middle)# Flip cooreindate - # uv.uv = pos - - # border_A[i] = uv_to_clusters[ connected_A[i][0] ] - # border_B[i] = uv_to_clusters[ connected_B[i][0] ] - for j in range(len(connected_A[i])): - border_A.append( connected_A[i][j] ) - border_B.append( connected_B[i][j] ) - - - - # for uv in clusters_B[idxB]: - # pos = clusters_A[idxA][0].uv.copy() - # # Flip cooreindate - # pos.x = x_middle - (pos.x-x_middle) - # uv.uv = pos + if True:#isAToB: + # Copy A side to B + for cluster in connected_B[i]: + for uv in cluster.uvs: + pos = connected_A[i][0].uvs[0].uv.copy() + pos.x = x_middle - (pos.x-x_middle)# Flip cooreindate + # uv.uv = pos + + # border_A[i] = uv_to_clusters[ connected_A[i][0] ] + # border_B[i] = uv_to_clusters[ connected_B[i][0] ] + for j in range(len(connected_A[i])): + border_A.append( connected_A[i][j] ) + border_B.append( connected_B[i][j] ) + + + + # for uv in clusters_B[idxB]: + # pos = clusters_A[idxA][0].uv.copy() + # # Flip cooreindate + # pos.x = x_middle - (pos.x-x_middle) + # uv.uv = pos - # for j in range(0, len(connected_A[i])): - # # Group A and B - # groupA = connected_A[i][j]; - # groupB = connected_B[i][j]; - # # vertexA = [vert_to_clusters[key] for key in vert_to_clusters if vert_to_clusters[key] == groupA] - # print("...map {} -> {}".format(groupA, groupB)) + # for j in range(0, len(connected_A[i])): + # # Group A and B + # groupA = connected_A[i][j]; + # groupB = connected_B[i][j]; + # # vertexA = [vert_to_clusters[key] for key in vert_to_clusters if vert_to_clusters[key] == groupA] + # print("...map {} -> {}".format(groupA, groupB)) - # for j in range(0, count): - # if len(connected_A[j]) != len(connected_B[j]): - # # print("Error: Inconsistent grow mappings from {}:{}x | {}:{}x".format(border_A[j].index,len(connected_A[j]), border_B[j].index, len(connected_B[j]) )) - # print("Error: Inconsistent grow mappings from {} {}x | {}x".format(j, len(connected_A[j]), len(connected_B[j]) )) - # continue + # for j in range(0, count): + # if len(connected_A[j]) != len(connected_B[j]): + # # print("Error: Inconsistent grow mappings from {}:{}x | {}:{}x".format(border_A[j].index,len(connected_A[j]), border_B[j].index, len(connected_B[j]) )) + # print("Error: Inconsistent grow mappings from {} {}x | {}x".format(j, len(connected_A[j]), len(connected_B[j]) )) + # continue - # for k in range(0, len(connected_A[j])): - # # Vertex A and B - # vA = connected_A[j][k]; - # vB = connected_B[j][k]; + # for k in range(0, len(connected_A[j])): + # # Vertex A and B + # vA = connected_A[j][k]; + # vB = connected_B[j][k]; - # uvsA = vert_to_uv[vA]; - # uvsB = vert_to_uv[vB]; + # uvsA = vert_to_uv[vA]; + # uvsB = vert_to_uv[vB]; - # clusters_A = collect_clusters(uvsA) - # clusters_B = collect_clusters(uvsB) + # clusters_A = collect_clusters(uvsA) + # clusters_B = collect_clusters(uvsB) - # if len(clusters_A) != len(clusters_B): - # print("Error: Inconsistent vertex UV group pairs at vertex {} : {}".format(vA.index, vB.index)) - # continue + # if len(clusters_A) != len(clusters_B): + # print("Error: Inconsistent vertex UV group pairs at vertex {} : {}".format(vA.index, vB.index)) + # continue - # message= "...Map {0} -> {1} = UVs {2}|{3}x | UV-Groups {4}x|{5}x".format( vA.index, vB.index, len(uvsA), len(uvsB), len(clusters_A), len(clusters_B) ) - # if len(clusters_A) > 1: - # message = ">> "+message - # print(message) - - - - # if len(clusters_A) > 0: - # # For each group - - # sortA = {} - # sortB = {} - # for g in range(0, len(clusters_A)): - # uv_A = clusters_A[g][0].uv.copy() - # uv_B = clusters_B[g][0].uv.copy() - - # # localize X values (from symmetry line) - # uv_A.x = (uv_A.x - x_middle) - # uv_B.x = (uv_B.x - x_middle) - - # sortA[g] = abs(uv_A.x) + uv_A.y*2.0 - # sortB[g] = abs(uv_B.x) + uv_B.y*2.0 - # # print(" . [{}] : {:.2f}, {:.2f} | {:.2f}, {:.2f}".format(g, uv_A.x, uv_A.y, uv_B.x, uv_B.y)) - # print(" . [{}] : {:.2f} | {:.2f}".format(g, sortA[g], sortB[g])) - - # # Sort sortA by value - # sortedA = sorted(sortA.items(), key=operator.itemgetter(1)) - # sortedB = sorted(sortB.items(), key=operator.itemgetter(1)) - - # for g in range(0, len(clusters_A)): - # # sortedA[g] - # idxA = sortedA[g][0] - # idxB = sortedB[g][0] - - # print("Map clusters_A {} -> ".format(idxA, idxB)) - # for uv in clusters_B[idxB]: - # pos = clusters_A[idxA][0].uv.copy() - # # Flip cooreindate - # pos.x = x_middle - (pos.x-x_middle) - # uv.uv = pos - - # border_A.append(vA) - # border_B.append(vB) - - - - print("--------------------------------") - ''' - - def select_extend_filter(verts_border, verts_mask): - # print("Extend A/B") - connected_verts = [] - for i in range(0, len(verts_border)): - # Collect connected edge verts - verts_connected_edges = [] - for edge in verts_border[i].link_edges: - if(edge.verts[0] not in verts_connected_edges): - verts_connected_edges.append(edge.verts[0]) - if(edge.verts[1] not in verts_connected_edges): - verts_connected_edges.append(edge.verts[1]) + # message= "...Map {0} -> {1} = UVs {2}|{3}x | UV-Groups {4}x|{5}x".format( vA.index, vB.index, len(uvsA), len(uvsB), len(clusters_A), len(clusters_B) ) + # if len(clusters_A) > 1: + # message = ">> "+message + # print(message) + + + + # if len(clusters_A) > 0: + # # For each group + + # sortA = {} + # sortB = {} + # for g in range(0, len(clusters_A)): + # uv_A = clusters_A[g][0].uv.copy() + # uv_B = clusters_B[g][0].uv.copy() + + # # localize X values (from symmetry line) + # uv_A.x = (uv_A.x - x_middle) + # uv_B.x = (uv_B.x - x_middle) + + # sortA[g] = abs(uv_A.x) + uv_A.y*2.0 + # sortB[g] = abs(uv_B.x) + uv_B.y*2.0 + # # print(" . [{}] : {:.2f}, {:.2f} | {:.2f}, {:.2f}".format(g, uv_A.x, uv_A.y, uv_B.x, uv_B.y)) + # print(" . [{}] : {:.2f} | {:.2f}".format(g, sortA[g], sortB[g])) + + # # Sort sortA by value + # sortedA = sorted(sortA.items(), key=operator.itemgetter(1)) + # sortedB = sorted(sortB.items(), key=operator.itemgetter(1)) + + # for g in range(0, len(clusters_A)): + # # sortedA[g] + # idxA = sortedA[g][0] + # idxB = sortedB[g][0] + + # print("Map clusters_A {} -> ".format(idxA, idxB)) + # for uv in clusters_B[idxB]: + # pos = clusters_A[idxA][0].uv.copy() + # # Flip cooreindate + # pos.x = x_middle - (pos.x-x_middle) + # uv.uv = pos + + # border_A.append(vA) + # border_B.append(vB) + + + + print("--------------------------------") + ''' + + def select_extend_filter(verts_border, verts_mask): + # print("Extend A/B") + connected_verts = [] + for i in range(0, len(verts_border)): + # Collect connected edge verts + verts_connected_edges = [] + for edge in verts_border[i].link_edges: + if(edge.verts[0] not in verts_connected_edges): + verts_connected_edges.append(edge.verts[0]) + if(edge.verts[1] not in verts_connected_edges): + verts_connected_edges.append(edge.verts[1]) - # Select vert on border - bpy.ops.mesh.select_all(action='DESELECT') - verts_border[i].select = True - + # Select vert on border + bpy.ops.mesh.select_all(action='DESELECT') + verts_border[i].select = True + - # Extend selection - bpy.ops.mesh.select_more() + # Extend selection + bpy.ops.mesh.select_more() - # Filter selected verts against mask, connected edges, processed and border - verts_extended = [vert for vert in bm.verts if (vert.select and vert in verts_connected_edges and vert in verts_mask and vert and vert not in verts_border and vert not in verts_processed)] - + # Filter selected verts against mask, connected edges, processed and border + verts_extended = [vert for vert in bm.verts if (vert.select and vert in verts_connected_edges and vert in verts_mask and vert and vert not in verts_border and vert not in verts_processed)] + - # print(" "+str(i)+". scan: "+str(verts_border[i].index)+"; ext: "+str(len(verts_extended))+"x") + # print(" "+str(i)+". scan: "+str(verts_border[i].index)+"; ext: "+str(len(verts_extended))+"x") - connected_verts.append( [] ) + connected_verts.append( [] ) - # Sort by distance - verts_distance = {} - for vert in verts_extended: - verts_distance[vert] = (verts_border[i].co - vert.co).length + # Sort by distance + verts_distance = {} + for vert in verts_extended: + verts_distance[vert] = (verts_border[i].co - vert.co).length - for item in sorted(verts_distance.items(), key=operator.itemgetter(1)): - connected_verts[i].append( item[0] ) - - if verts_border[i] not in verts_processed: - verts_processed.append(verts_border[i]) - - return connected_verts - - # find UV vert blobs , see which ones are same spot - def collect_clusters(uvs): - groups = [] - for uv in uvs: - if len(groups) == 0: - groups.append([uv]) - else: - isMerged = False - for group in groups: - d = (uv.uv - group[0].uv).length - if d <= 0.0000001: - #Merge - group.append(uv) - isMerged = True; - break; - if not isMerged: - #New Group - groups.append([uv]) - return groups - - - border_A = [vert for vert in verts_middle] - border_B = [vert for vert in verts_middle] - - - for i in range(0, 200): - - if len(border_A) == 0: - print("Finished scanning at {} growth iterations".format(i)) - break; - if len(border_A) != len(border_B) or len(border_A) == 0: - print("Abort: non compatible border A/B: {}x {}x ".format(len(border_A), len(border_B))) - break; - - connected_A = select_extend_filter(border_A, verts_A) - connected_B = select_extend_filter(border_B, verts_B) - - print("Map pairs: {}|{}".format(len(connected_A), len(connected_B))) - - border_A.clear() - border_B.clear() - - count = min(len(connected_A), len(connected_B)) - for j in range(0, count): - if len(connected_A[j]) != len(connected_B[j]): - # print("Error: Inconsistent grow mappings from {}:{}x | {}:{}x".format(border_A[j].index,len(connected_A[j]), border_B[j].index, len(connected_B[j]) )) - print("Error: Inconsistent grow mappings from {} {}x | {}x".format(j, len(connected_A[j]), len(connected_B[j]) )) - continue - - for k in range(0, len(connected_A[j])): - # Vertex A and B - vA = connected_A[j][k]; - vB = connected_B[j][k]; - - uvsA = vert_to_uv[vA]; - uvsB = vert_to_uv[vB]; - - clusters_A = collect_clusters(uvsA) - clusters_B = collect_clusters(uvsB) - - if len(clusters_A) != len(clusters_B): - print("Error: Inconsistent vertex UV group pairs at vertex {} : {}".format(vA.index, vB.index)) - continue - - - message= "...Map {0} -> {1} = UVs {2}|{3}x | UV-Groups {4}x|{5}x".format( vA.index, vB.index, len(uvsA), len(uvsB), len(clusters_A), len(clusters_B) ) - if len(clusters_A) > 1: - message = ">> "+message - print(message) - - - - if len(clusters_A) > 0: - # For each group - - - - sortA = {} - sortB = {} - for g in range(0, len(clusters_A)): - uv_A = clusters_A[g][0].uv.copy() - uv_B = clusters_B[g][0].uv.copy() - - # localize X values (from symmetry line) - uv_A.x = (uv_A.x - x_middle) - uv_B.x = (uv_B.x - x_middle) - - sortA[g] = abs(uv_A.x) + uv_A.y*2.0 - sortB[g] = abs(uv_B.x) + uv_B.y*2.0 - # print(" . [{}] : {:.2f}, {:.2f} | {:.2f}, {:.2f}".format(g, uv_A.x, uv_A.y, uv_B.x, uv_B.y)) - print(" . [{}] : {:.2f} | {:.2f}".format(g, sortA[g], sortB[g])) - - # Sort sortA by value - sortedA = sorted(sortA.items(), key=operator.itemgetter(1)) - sortedB = sorted(sortB.items(), key=operator.itemgetter(1)) - - for g in range(0, len(clusters_A)): - # sortedA[g] - idxA = sortedA[g][0] - idxB = sortedB[g][0] - - print("Map clusters_A {} -> ".format(idxA, idxB)) - for uv in clusters_B[idxB]: - pos = clusters_A[idxA][0].uv.copy() - # Flip cooreindate - pos.x = x_middle - (pos.x-x_middle) - uv.uv = pos - - - # print("Sorted: '"+str(sortedA)+"'") - # print("Sorted: '"+str(sortedB)+"'") - - # for item in sorted(verts_distance.items(), key=operator.itemgetter(1)): - # connected_verts[i].append( item[0] ) - - # TODO: Now map groups to each other - # uv_avg_A = Vector([0,0]) - # uv_avg_B = Vector([0,0]) - # for m in range(0, len(clusters_A)): - # print(" . ") - # uv_avg_A+= clusters_A[m][0].uv; - # uv_avg_B+= clusters_B[m][0].uv; - - # uv_avg_A/=len(clusters_A) - # uv_avg_B/=len(clusters_B) - - # print(" avg: {} : {}".format(uv_avg_A, uv_avg_B)) - - # Done processing, add to border arrays - border_A.append(vA) - border_B.append(vB) + for item in sorted(verts_distance.items(), key=operator.itemgetter(1)): + connected_verts[i].append( item[0] ) + + if verts_border[i] not in verts_processed: + verts_processed.append(verts_border[i]) + + return connected_verts + + # find UV vert blobs , see which ones are same spot + def collect_clusters(uvs): + groups = [] + for uv in uvs: + if len(groups) == 0: + groups.append([uv]) + else: + isMerged = False + for group in groups: + d = (uv.uv - group[0].uv).length + if d <= 0.0000001: + #Merge + group.append(uv) + isMerged = True; + break; + if not isMerged: + #New Group + groups.append([uv]) + return groups + + + border_A = [vert for vert in verts_middle] + border_B = [vert for vert in verts_middle] + + + for i in range(0, 200): + + if len(border_A) == 0: + print("Finished scanning at {} growth iterations".format(i)) + break; + if len(border_A) != len(border_B) or len(border_A) == 0: + print("Abort: non compatible border A/B: {}x {}x ".format(len(border_A), len(border_B))) + break; + + connected_A = select_extend_filter(border_A, verts_A) + connected_B = select_extend_filter(border_B, verts_B) + + print("Map pairs: {}|{}".format(len(connected_A), len(connected_B))) + + border_A.clear() + border_B.clear() + + count = min(len(connected_A), len(connected_B)) + for j in range(0, count): + if len(connected_A[j]) != len(connected_B[j]): + # print("Error: Inconsistent grow mappings from {}:{}x | {}:{}x".format(border_A[j].index,len(connected_A[j]), border_B[j].index, len(connected_B[j]) )) + print("Error: Inconsistent grow mappings from {} {}x | {}x".format(j, len(connected_A[j]), len(connected_B[j]) )) + continue + + for k in range(0, len(connected_A[j])): + # Vertex A and B + vA = connected_A[j][k]; + vB = connected_B[j][k]; + + uvsA = vert_to_uv[vA]; + uvsB = vert_to_uv[vB]; + + clusters_A = collect_clusters(uvsA) + clusters_B = collect_clusters(uvsB) + + if len(clusters_A) != len(clusters_B): + print("Error: Inconsistent vertex UV group pairs at vertex {} : {}".format(vA.index, vB.index)) + continue + + + message= "...Map {0} -> {1} = UVs {2}|{3}x | UV-Groups {4}x|{5}x".format( vA.index, vB.index, len(uvsA), len(uvsB), len(clusters_A), len(clusters_B) ) + if len(clusters_A) > 1: + message = ">> "+message + print(message) + + + + if len(clusters_A) > 0: + # For each group + + + + sortA = {} + sortB = {} + for g in range(0, len(clusters_A)): + uv_A = clusters_A[g][0].uv.copy() + uv_B = clusters_B[g][0].uv.copy() + + # localize X values (from symmetry line) + uv_A.x = (uv_A.x - x_middle) + uv_B.x = (uv_B.x - x_middle) + + sortA[g] = abs(uv_A.x) + uv_A.y*2.0 + sortB[g] = abs(uv_B.x) + uv_B.y*2.0 + # print(" . [{}] : {:.2f}, {:.2f} | {:.2f}, {:.2f}".format(g, uv_A.x, uv_A.y, uv_B.x, uv_B.y)) + print(" . [{}] : {:.2f} | {:.2f}".format(g, sortA[g], sortB[g])) + + # Sort sortA by value + sortedA = sorted(sortA.items(), key=operator.itemgetter(1)) + sortedB = sorted(sortB.items(), key=operator.itemgetter(1)) + + for g in range(0, len(clusters_A)): + # sortedA[g] + idxA = sortedA[g][0] + idxB = sortedB[g][0] + + print("Map clusters_A {} -> ".format(idxA, idxB)) + for uv in clusters_B[idxB]: + pos = clusters_A[idxA][0].uv.copy() + # Flip cooreindate + pos.x = x_middle - (pos.x-x_middle) + uv.uv = pos + + + # print("Sorted: '"+str(sortedA)+"'") + # print("Sorted: '"+str(sortedB)+"'") + + # for item in sorted(verts_distance.items(), key=operator.itemgetter(1)): + # connected_verts[i].append( item[0] ) + + # TODO: Now map groups to each other + # uv_avg_A = Vector([0,0]) + # uv_avg_B = Vector([0,0]) + # for m in range(0, len(clusters_A)): + # print(" . ") + # uv_avg_A+= clusters_A[m][0].uv; + # uv_avg_B+= clusters_B[m][0].uv; + + # uv_avg_A/=len(clusters_A) + # uv_avg_B/=len(clusters_B) + + # print(" avg: {} : {}".format(uv_avg_A, uv_avg_B)) + + # Done processing, add to border arrays + border_A.append(vA) + border_B.append(vB) - ''' + ''' def alignToCenterLine(): - print("align to center line") + print("align to center line") - bm = bmesh.from_edit_mesh(bpy.context.active_object.data) - uv_layers = bm.loops.layers.uv.verify() - bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE') + bm = bmesh.from_edit_mesh(bpy.context.active_object.data) + uv_layers = bm.loops.layers.uv.verify() + bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE') - # 1.) Get average edges rotation + center - average_angle = 0 - average_center = Vector((0,0)) - average_count = 0 - for face in bm.faces: - if face.select: - verts = [] - for loop in face.loops: - if loop[uv_layers].select: - verts.append(loop[uv_layers].uv) + # 1.) Get average edges rotation + center + average_angle = 0 + average_center = Vector((0,0)) + average_count = 0 + for face in bm.faces: + if face.select: + verts = [] + for loop in face.loops: + if loop[uv_layers].select: + verts.append(loop[uv_layers].uv) - if len(verts) == 2: - diff = verts[1] - verts[0] - angle = math.atan2(diff.y, diff.x)%(math.pi) - average_center += verts[0] + diff/2 - average_angle += angle - average_count+=1 + if len(verts) == 2: + diff = verts[1] - verts[0] + angle = math.atan2(diff.y, diff.x)%(math.pi) + average_center += verts[0] + diff/2 + average_angle += angle + average_count+=1 - if average_count >0: - average_angle/=average_count - average_center/=average_count + if average_count >0: + average_angle/=average_count + average_center/=average_count - average_angle-= math.pi/2 #Rotate -90 degrees so aligned horizontally + average_angle-= math.pi/2 #Rotate -90 degrees so aligned horizontally - # 2.) Rotate UV Shell around edge - bpy.context.tool_settings.transform_pivot_point = 'CURSOR' - bpy.ops.uv.cursor_set(location=average_center) + # 2.) Rotate UV Shell around edge + bpy.context.tool_settings.transform_pivot_point = 'CURSOR' + bpy.ops.uv.cursor_set(location=average_center) - bpy.ops.uv.select_linked() - bpy.ops.transform.rotate(value=average_angle, orient_axis='Z', constraint_axis=(False, False, False), orient_type='GLOBAL', mirror=False, use_proportional_edit=False) + bpy.ops.uv.select_linked() + bpy.ops.transform.rotate(value=average_angle, orient_axis='Z', constraint_axis=(False, False, False), orient_type='GLOBAL', mirror=False, use_proportional_edit=False) class UVCluster: - uvs = [] - vertex = None - - def __init__(self, vertex, uvs): - self.vertex = vertex - self.uvs = uvs - - def append(self, uv): - self.uvs.append(uv) + uvs = [] + vertex = None + + def __init__(self, vertex, uvs): + self.vertex = vertex + self.uvs = uvs + + def append(self, uv): + self.uvs.append(uv) bpy.utils.register_class(op) \ No newline at end of file diff --git a/op_island_rotate_90.py b/op_island_rotate_90.py index 1fd5f56..88b07e9 100644 --- a/op_island_rotate_90.py +++ b/op_island_rotate_90.py @@ -9,73 +9,73 @@ from math import pi from . import utilities_uv class op(bpy.types.Operator): - bl_idname = "uv.textools_island_rotate_90" - bl_label = "Rotate 90 degrees" - bl_description = "Rotate the selected UV island 90 degrees left or right" - bl_options = {'REGISTER', 'UNDO'} - - angle : bpy.props.FloatProperty(name="Angle") + bl_idname = "uv.textools_island_rotate_90" + bl_label = "Rotate 90 degrees" + bl_description = "Rotate the selected UV island 90 degrees left or right" + bl_options = {'REGISTER', 'UNDO'} + + angle : bpy.props.FloatProperty(name="Angle") - @classmethod - def poll(cls, context): - #Only in UV editor mode - if bpy.context.area.type != 'IMAGE_EDITOR': - return False + @classmethod + def poll(cls, context): + #Only in UV editor mode + if bpy.context.area.type != 'IMAGE_EDITOR': + return False - if not bpy.context.active_object: - return False + if not bpy.context.active_object: + return False - if bpy.context.active_object.type != 'MESH': - return False + if bpy.context.active_object.type != 'MESH': + return False - #Only in Edit mode - if bpy.context.active_object.mode != 'EDIT': - return False + #Only in Edit mode + if bpy.context.active_object.mode != 'EDIT': + return False - #Requires UV map - if not bpy.context.object.data.uv_layers: - return False - - # Not in Synced mode - if bpy.context.scene.tool_settings.use_uv_select_sync: - return False + #Requires UV map + if not bpy.context.object.data.uv_layers: + return False + + # Not in Synced mode + if bpy.context.scene.tool_settings.use_uv_select_sync: + return False - return True + return True - def execute(self, context): + def execute(self, context): - main(context, self.angle) - return {'FINISHED'} + main(context, self.angle) + return {'FINISHED'} def main(context, angle): - - #Store selection - utilities_uv.selection_store() - - bm = bmesh.from_edit_mesh(bpy.context.active_object.data) - uv_layers = bm.loops.layers.uv.verify() - - bpy.ops.uv.select_linked() - - #Bounds - bounds_initial = utilities_uv.getSelectionBBox() - bpy.ops.transform.rotate(value=angle, orient_axis='Z', constraint_axis=(False, False, False), use_proportional_edit=False) - - #Align rotation to top left|right - bounds_post = utilities_uv.getSelectionBBox() - dy = bounds_post['max'].y - bounds_initial['max'].y - dx = 0 - if angle > 0: - dx = bounds_post['max'].x - bounds_initial['max'].x - else: - dx = bounds_post['min'].x - bounds_initial['min'].x - bpy.ops.transform.translate(value=(-dx, -dy, 0), constraint_axis=(False, False, False), use_proportional_edit=False) - - - #Restore selection - utilities_uv.selection_restore() + + #Store selection + utilities_uv.selection_store() + + bm = bmesh.from_edit_mesh(bpy.context.active_object.data) + uv_layers = bm.loops.layers.uv.verify() + + bpy.ops.uv.select_linked() + + #Bounds + bounds_initial = utilities_uv.getSelectionBBox() + bpy.ops.transform.rotate(value=angle, orient_axis='Z', constraint_axis=(False, False, False), use_proportional_edit=False) + + #Align rotation to top left|right + bounds_post = utilities_uv.getSelectionBBox() + dy = bounds_post['max'].y - bounds_initial['max'].y + dx = 0 + if angle > 0: + dx = bounds_post['max'].x - bounds_initial['max'].x + else: + dx = bounds_post['min'].x - bounds_initial['min'].x + bpy.ops.transform.translate(value=(-dx, -dy, 0), constraint_axis=(False, False, False), use_proportional_edit=False) + + + #Restore selection + utilities_uv.selection_restore() bpy.utils.register_class(op) \ No newline at end of file diff --git a/op_island_straighten_edge_loops.py b/op_island_straighten_edge_loops.py index 6b20574..8dbf170 100644 --- a/op_island_straighten_edge_loops.py +++ b/op_island_straighten_edge_loops.py @@ -9,216 +9,216 @@ from math import pi from . import utilities_uv class op(bpy.types.Operator): - bl_idname = "uv.textools_island_straighten_edge_loops" - bl_label = "Straight edge loops" - bl_description = "Straighten edge loops of UV Island and relax rest" - bl_options = {'REGISTER', 'UNDO'} - - @classmethod - def poll(cls, context): + bl_idname = "uv.textools_island_straighten_edge_loops" + bl_label = "Straight edge loops" + bl_description = "Straighten edge loops of UV Island and relax rest" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(cls, context): - if not bpy.context.active_object: - return False + if not bpy.context.active_object: + return False - if bpy.context.active_object.type != 'MESH': - return False + if bpy.context.active_object.type != 'MESH': + return False - #Only in Edit mode - if bpy.context.active_object.mode != 'EDIT': - return False + #Only in Edit mode + if bpy.context.active_object.mode != 'EDIT': + return False - #Only in UV editor mode - if bpy.context.area.type != 'IMAGE_EDITOR': - return False + #Only in UV editor mode + if bpy.context.area.type != 'IMAGE_EDITOR': + return False - #Requires UV map - if not bpy.context.object.data.uv_layers: - return False + #Requires UV map + if not bpy.context.object.data.uv_layers: + return False - if bpy.context.scene.tool_settings.uv_select_mode != 'EDGE': - return False + if bpy.context.scene.tool_settings.uv_select_mode != 'EDGE': + return False - return True + return True - def execute(self, context): + def execute(self, context): - main(context) - return {'FINISHED'} + main(context) + return {'FINISHED'} def main(context): - print("____________________________") - - #Store selection - utilities_uv.selection_store() + print("____________________________") + + #Store selection + utilities_uv.selection_store() - bm = bmesh.from_edit_mesh(bpy.context.active_object.data) - uv_layers = bm.loops.layers.uv.verify() - - edges = utilities_uv.get_selected_uv_edges(bm, uv_layers) - islands = utilities_uv.getSelectionIslands() - uvs = utilities_uv.get_selected_uvs(bm, uv_layers) - faces = [f for island in islands for f in island ] + bm = bmesh.from_edit_mesh(bpy.context.active_object.data) + uv_layers = bm.loops.layers.uv.verify() + + edges = utilities_uv.get_selected_uv_edges(bm, uv_layers) + islands = utilities_uv.getSelectionIslands() + uvs = utilities_uv.get_selected_uvs(bm, uv_layers) + faces = [f for island in islands for f in island ] - # Get island faces - + # Get island faces + - # utilities_uv.selection_restore(bm, uv_layers) + # utilities_uv.selection_restore(bm, uv_layers) - groups = get_edge_groups(bm, uv_layers, faces, edges, uvs) + groups = get_edge_groups(bm, uv_layers, faces, edges, uvs) - bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE') - bpy.ops.mesh.select_all(action='DESELECT') - for face in faces: - face.select = True + bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE') + bpy.ops.mesh.select_all(action='DESELECT') + for face in faces: + face.select = True - print("Edges {}x".format(len(edges))) - print("Groups {}x".format(len(groups))) + print("Edges {}x".format(len(edges))) + print("Groups {}x".format(len(groups))) - # Restore 3D face selection - + # Restore 3D face selection + - + - # Restore UV seams and clear pins - bpy.ops.uv.seams_from_islands() - bpy.ops.uv.pin(clear=True) + # Restore UV seams and clear pins + bpy.ops.uv.seams_from_islands() + bpy.ops.uv.pin(clear=True) - edge_sets = [] - for edges in groups: - edge_sets.append( EdgeSet(bm, uv_layers, edges, faces) ) - # straighten_edges(bm, uv_layers, edges, faces) + edge_sets = [] + for edges in groups: + edge_sets.append( EdgeSet(bm, uv_layers, edges, faces) ) + # straighten_edges(bm, uv_layers, edges, faces) - + - sorted_sets = sorted(edge_sets, key=lambda x: x.length, reverse=True) + sorted_sets = sorted(edge_sets, key=lambda x: x.length, reverse=True) - for edge_set in sorted_sets: - edge_set.straighten() - - #Restore selection - utilities_uv.selection_restore() - + for edge_set in sorted_sets: + edge_set.straighten() + + #Restore selection + utilities_uv.selection_restore() + class EdgeSet: - bm = None - edges = [] - faces = [] - uv_layers = '' - vert_to_uv = {} - edge_length = {} - length = 0 - - def __init__(self, bm, uv_layers, edges, faces): - self.bm = bm - self.uv_layers = uv_layers - self.edges = edges - self.faces = faces - - # Get Vert to UV within faces - self.vert_to_uv = utilities_uv.get_vert_to_uv(bm, uv_layers) - - # Get edge lengths - self.edge_length = {} - self.length = 0 - for e in edges: - uv1 = self.vert_to_uv[e.verts[0]][0].uv - uv2 = self.vert_to_uv[e.verts[1]][0].uv - self.edge_length[e] = (uv2 - uv1).length - self.length+=self.edge_length[e] - - - def straighten(self): - print("Straight {}x at {:.2f} length ".format(len(self.edges), self.length)) - - # Get edge angles in UV space - angles = {} - for edge in self.edges: - uv1 = self.vert_to_uv[edge.verts[0]][0].uv - uv2 = self.vert_to_uv[edge.verts[1]][0].uv - delta = uv2 - uv1 - angle = math.atan2(delta.y, delta.x)%(math.pi/2) - if angle >= (math.pi/4): - angle = angle - (math.pi/2) - angles[edge] = abs(angle) - # print("Angle {:.2f} degr".format(angle * 180 / math.pi)) - - # Pick edge with least rotation offset to U or V axis - edge_main = sorted(angles.items(), key = operator.itemgetter(1))[0][0] - - print("Main edge: {} at {:.2f} degr".format( edge_main.index, angles[edge_main] * 180 / math.pi )) - - # Rotate main edge to closest axis - uvs = [uv for v in edge_main.verts for uv in self.vert_to_uv[v]] - bpy.ops.uv.select_all(action='DESELECT') - for uv in uvs: - uv.select = True - uv1 = self.vert_to_uv[edge_main.verts[0]][0].uv - uv2 = self.vert_to_uv[edge_main.verts[1]][0].uv - diff = uv2 - uv1 - angle = math.atan2(diff.y, diff.x)%(math.pi/2) - if angle >= (math.pi/4): - angle = angle - (math.pi/2) - bpy.ops.uv.cursor_set(location=uv1 + diff/2) - bpy.ops.transform.rotate(value=angle, orient_axis='Z', constraint_axis=(False, False, False), orient_type='GLOBAL', mirror=False, use_proportional_edit=False) - - # Expand edges and straighten - count = len(self.edges) - processed = [edge_main] - for i in range(count): - if(len(processed) < len(self.edges)): - verts = set([v for e in processed for v in e.verts]) - edges_expand = [e for e in self.edges if e not in processed and (e.verts[0] in verts or e.verts[1] in verts)] - verts_ends = [v for e in edges_expand for v in e.verts if v in verts] - - - print("Step, proc {} exp: {}".format( [e.index for e in processed] , [e.index for e in edges_expand] )) - - if len(edges_expand) == 0: - continue - - for edge in edges_expand: - # if edge.verts[0] in verts_ends and edge.verts[1] in verts_ends: - # print("Cancel at edge {}".format(edge.index)) - # return - - print(" E {} verts {} verts end: {}".format(edge.index, [v.index for v in edge.verts], [v.index for v in verts_ends])) - v1 = [v for v in edge.verts if v in verts_ends][0] - v2 = [v for v in edge.verts if v not in verts_ends][0] - # direction - previous_edge = [e for e in processed if e.verts[0] in edge.verts or e.verts[1] in edge.verts][0] - prev_v1 = [v for v in previous_edge.verts if v != v1][0] - prev_v2 = [v for v in previous_edge.verts if v == v1][0] - direction = (self.vert_to_uv[prev_v2][0].uv - self.vert_to_uv[prev_v1][0].uv).normalized() - - for uv in self.vert_to_uv[v2]: - uv.uv = self.vert_to_uv[v1][0].uv + direction * self.edge_length[edge] - - print("Procesed {}x Expand {}x".format(len(processed), len(edges_expand) )) - print("verts_ends: {}x".format(len(verts_ends))) - processed.extend(edges_expand) - - # Select edges - uvs = list(set( [uv for e in self.edges for v in e.verts for uv in self.vert_to_uv[v] ] )) - bpy.ops.uv.select_all(action='DESELECT') - for uv in uvs: - uv.select = True - - # Pin UV's - bpy.ops.uv.pin() - bpy.ops.uv.unwrap(method='ANGLE_BASED', margin=0.001) - bpy.ops.uv.pin(clear=True) + bm = None + edges = [] + faces = [] + uv_layers = '' + vert_to_uv = {} + edge_length = {} + length = 0 + + def __init__(self, bm, uv_layers, edges, faces): + self.bm = bm + self.uv_layers = uv_layers + self.edges = edges + self.faces = faces + + # Get Vert to UV within faces + self.vert_to_uv = utilities_uv.get_vert_to_uv(bm, uv_layers) + + # Get edge lengths + self.edge_length = {} + self.length = 0 + for e in edges: + uv1 = self.vert_to_uv[e.verts[0]][0].uv + uv2 = self.vert_to_uv[e.verts[1]][0].uv + self.edge_length[e] = (uv2 - uv1).length + self.length+=self.edge_length[e] + + + def straighten(self): + print("Straight {}x at {:.2f} length ".format(len(self.edges), self.length)) + + # Get edge angles in UV space + angles = {} + for edge in self.edges: + uv1 = self.vert_to_uv[edge.verts[0]][0].uv + uv2 = self.vert_to_uv[edge.verts[1]][0].uv + delta = uv2 - uv1 + angle = math.atan2(delta.y, delta.x)%(math.pi/2) + if angle >= (math.pi/4): + angle = angle - (math.pi/2) + angles[edge] = abs(angle) + # print("Angle {:.2f} degr".format(angle * 180 / math.pi)) + + # Pick edge with least rotation offset to U or V axis + edge_main = sorted(angles.items(), key = operator.itemgetter(1))[0][0] + + print("Main edge: {} at {:.2f} degr".format( edge_main.index, angles[edge_main] * 180 / math.pi )) + + # Rotate main edge to closest axis + uvs = [uv for v in edge_main.verts for uv in self.vert_to_uv[v]] + bpy.ops.uv.select_all(action='DESELECT') + for uv in uvs: + uv.select = True + uv1 = self.vert_to_uv[edge_main.verts[0]][0].uv + uv2 = self.vert_to_uv[edge_main.verts[1]][0].uv + diff = uv2 - uv1 + angle = math.atan2(diff.y, diff.x)%(math.pi/2) + if angle >= (math.pi/4): + angle = angle - (math.pi/2) + bpy.ops.uv.cursor_set(location=uv1 + diff/2) + bpy.ops.transform.rotate(value=angle, orient_axis='Z', constraint_axis=(False, False, False), orient_type='GLOBAL', mirror=False, use_proportional_edit=False) + + # Expand edges and straighten + count = len(self.edges) + processed = [edge_main] + for i in range(count): + if(len(processed) < len(self.edges)): + verts = set([v for e in processed for v in e.verts]) + edges_expand = [e for e in self.edges if e not in processed and (e.verts[0] in verts or e.verts[1] in verts)] + verts_ends = [v for e in edges_expand for v in e.verts if v in verts] + + + print("Step, proc {} exp: {}".format( [e.index for e in processed] , [e.index for e in edges_expand] )) + + if len(edges_expand) == 0: + continue + + for edge in edges_expand: + # if edge.verts[0] in verts_ends and edge.verts[1] in verts_ends: + # print("Cancel at edge {}".format(edge.index)) + # return + + print(" E {} verts {} verts end: {}".format(edge.index, [v.index for v in edge.verts], [v.index for v in verts_ends])) + v1 = [v for v in edge.verts if v in verts_ends][0] + v2 = [v for v in edge.verts if v not in verts_ends][0] + # direction + previous_edge = [e for e in processed if e.verts[0] in edge.verts or e.verts[1] in edge.verts][0] + prev_v1 = [v for v in previous_edge.verts if v != v1][0] + prev_v2 = [v for v in previous_edge.verts if v == v1][0] + direction = (self.vert_to_uv[prev_v2][0].uv - self.vert_to_uv[prev_v1][0].uv).normalized() + + for uv in self.vert_to_uv[v2]: + uv.uv = self.vert_to_uv[v1][0].uv + direction * self.edge_length[edge] + + print("Procesed {}x Expand {}x".format(len(processed), len(edges_expand) )) + print("verts_ends: {}x".format(len(verts_ends))) + processed.extend(edges_expand) + + # Select edges + uvs = list(set( [uv for e in self.edges for v in e.verts for uv in self.vert_to_uv[v] ] )) + bpy.ops.uv.select_all(action='DESELECT') + for uv in uvs: + uv.select = True + + # Pin UV's + bpy.ops.uv.pin() + bpy.ops.uv.unwrap(method='ANGLE_BASED', margin=0.001) + bpy.ops.uv.pin(clear=True) @@ -228,43 +228,43 @@ class EdgeSet: def get_edge_groups(bm, uv_layers, faces, edges, uvs): - print("Get edge groups, edges {}x".format(len(edges))+"x") + print("Get edge groups, edges {}x".format(len(edges))+"x") - bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE') + bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE') - unmatched = edges.copy() + unmatched = edges.copy() - groups = [] + groups = [] - for edge in edges: - if edge in unmatched: + for edge in edges: + if edge in unmatched: - # Loop select edge - bpy.ops.mesh.select_all(action='DESELECT') - edge.select = True - bpy.ops.mesh.loop_multi_select(ring=False) + # Loop select edge + bpy.ops.mesh.select_all(action='DESELECT') + edge.select = True + bpy.ops.mesh.loop_multi_select(ring=False) - # Isolate group within edges - group = [e for e in bm.edges if e.select and e in edges] - groups.append(group) + # Isolate group within edges + group = [e for e in bm.edges if e.select and e in edges] + groups.append(group) - # Remove from unmatched - for e in group: - if e in unmatched: - unmatched.remove(e) + # Remove from unmatched + for e in group: + if e in unmatched: + unmatched.remove(e) - print(" Edge {} : Group: {}x , unmatched: {}".format(edge.index, len(group), len(unmatched))) + print(" Edge {} : Group: {}x , unmatched: {}".format(edge.index, len(group), len(unmatched))) - # return - # group = [edge] - # for e in bm.edges: - # if e.select and e in unmatched: - # unmatched.remove(e) - # group.append(edge) + # return + # group = [edge] + # for e in bm.edges: + # if e.select and e in unmatched: + # unmatched.remove(e) + # group.append(edge) - - - return groups + + + return groups bpy.utils.register_class(op) diff --git a/op_meshtex_create.py b/op_meshtex_create.py index 6a2b70b..dd4a4ce 100644 --- a/op_meshtex_create.py +++ b/op_meshtex_create.py @@ -11,286 +11,286 @@ from . import utilities_meshtex def get_mode(): - if not utilities_meshtex.find_uv_mesh([bpy.context.active_object]): - # Create UV mesh from face selection - if bpy.context.active_object and bpy.context.active_object.mode == 'EDIT': - return 'FACES' + if not utilities_meshtex.find_uv_mesh([bpy.context.active_object]): + # Create UV mesh from face selection + if bpy.context.active_object and bpy.context.active_object.mode == 'EDIT': + return 'FACES' - # Create UV mesh from whole object - if bpy.context.active_object and bpy.context.active_object.type == 'MESH': - if "SurfaceDeform" not in bpy.context.active_object.modifiers: - return 'OBJECT' + # Create UV mesh from whole object + if bpy.context.active_object and bpy.context.active_object.type == 'MESH': + if "SurfaceDeform" not in bpy.context.active_object.modifiers: + return 'OBJECT' - return 'UNDEFINED' + return 'UNDEFINED' class op(bpy.types.Operator): - bl_idname = "uv.textools_meshtex_create" - bl_label = "UV Mesh" - bl_description = "Create a new UV Mesh from your selected object" - bl_options = {'REGISTER', 'UNDO'} + bl_idname = "uv.textools_meshtex_create" + bl_label = "UV Mesh" + bl_description = "Create a new UV Mesh from your selected object" + bl_options = {'REGISTER', 'UNDO'} - @classmethod - def poll(cls, context): - if get_mode() == 'UNDEFINED': - return False - return True + @classmethod + def poll(cls, context): + if get_mode() == 'UNDEFINED': + return False + return True - def execute(self, context): - create_uv_mesh(self, bpy.context.active_object) - return {'FINISHED'} + def execute(self, context): + create_uv_mesh(self, bpy.context.active_object) + return {'FINISHED'} def create_uv_mesh(self, obj): - - mode = bpy.context.active_object.mode - - # Select - bpy.ops.object.mode_set(mode='OBJECT') - bpy.ops.object.select_all(action='DESELECT') - obj.select_set( state = True, view_layer = None) - bpy.context.view_layer.objects.active = obj - - - bpy.ops.object.mode_set(mode='EDIT') - bpy.ops.mesh.select_mode(type="FACE") - bpy.context.scene.tool_settings.use_uv_select_sync = False - - - # Select all if OBJECT mode - if mode == 'OBJECT': - bpy.ops.mesh.select_all(action='SELECT') - # bpy.ops.uv.select_all(action='SELECT') - - # Create UV Map - if not obj.data.uv_layers: - if mode == 'OBJECT': - # Smart UV project - bpy.ops.uv.smart_project( - angle_limit=65, - island_margin=0.5, - user_area_weight=0, - use_aspect=True, - stretch_to_bounds=True - ) - elif mode == 'EDIT': - # Iron Faces - bpy.ops.uv.unwrap(method='ANGLE_BASED', margin=0) - bpy.ops.uv.textools_unwrap_faces_iron() - - - bm = bmesh.from_edit_mesh(obj.data) - uv_layers = bm.loops.layers.uv.verify() - - #Collect UV islands - bpy.ops.uv.select_all(action='SELECT') - islands = utilities_uv.getSelectionIslands(bm, uv_layers) - - # Collect clusters - uvs = {} - clusters = [] - uv_to_clusters = {} - vert_to_clusters = {} - - face_area_view = 0 - face_area_uv = 0 - - for face in bm.faces: - if face.select: - # Calculate triangle area for UV and View - # Triangle Verts - tri_uv = [loop[uv_layers].uv for loop in face.loops ] - tri_vt = [vert.co for vert in face.verts] - - #Triangle Areas - face_area_view += math.sqrt(utilities_texel.get_area_triangle( - tri_vt[0], - tri_vt[1], - tri_vt[2] - )) - face_area_uv += math.sqrt(utilities_texel.get_area_triangle( - tri_uv[0], - tri_uv[1], - tri_uv[2] - )) - - for i in range(len(face.loops)): - v = face.loops[i] - uv = Get_UVSet(uvs, bm, uv_layers, face.index, i) - - # # clusters - isMerged = False - for cluster in clusters: - d = (uv.pos() - cluster.uvs[0].pos()).length - if d <= 0.0000001: - #Merge - cluster.append(uv) - uv_to_clusters[uv] = cluster - if v not in vert_to_clusters: - vert_to_clusters[v] = cluster - isMerged = True; - break; - if not isMerged: - #New Group - clusters.append( UVCluster(v, [uv]) ) - uv_to_clusters[uv] = clusters[-1] - if v not in vert_to_clusters: - vert_to_clusters[v] = clusters[-1] - - scale = face_area_view / face_area_uv - - print("Scale {}x {} | {}".format(scale, face_area_view, face_area_uv)) - print("Islands {}x".format(len(islands))) - print("UV Vert Clusters {}x".format(len(clusters))) - - m_vert_cluster = [] - m_verts_org = [] - m_verts_A = [] - m_verts_B = [] - m_faces = [] - - for island in islands: - for face in island: - f = [] - for i in range(len(face.loops)): - v = face.loops[i].vert - uv = Get_UVSet(uvs, bm, uv_layers, face.index, i) - c = uv_to_clusters[ uv ] - - index = 0 - if c in m_vert_cluster: - index = m_vert_cluster.index(c) - - else: - index = len(m_vert_cluster) - m_vert_cluster.append(c) - m_verts_org.append(v) - - m_verts_A.append( Vector((uv.pos().x*scale - scale/2, uv.pos().y*scale -scale/2, 0)) ) - m_verts_B.append( obj.matrix_world @ v.co - bpy.context.scene.cursor.location ) - - f.append(index) - - m_faces.append(f) - - # Add UV bounds as edges - verts = [ - Vector((-scale/2, -scale/2, 0)), - Vector(( scale/2, -scale/2, 0)), - Vector(( scale/2, scale/2, 0)), - Vector((-scale/2, scale/2, 0)), - ] - m_verts_A = m_verts_A+verts; - m_verts_B = m_verts_B+verts; - - bpy.ops.object.mode_set(mode='OBJECT') - - # Create Mesh - mesh = bpy.data.meshes.new("mesh_texture") - mesh.from_pydata(m_verts_A, [], m_faces) - mesh.update() - mesh_obj = bpy.data.objects.new("UV_mesh {0}".format(obj.name), mesh) - mesh_obj.location = bpy.context.scene.cursor.location - bpy.context.collection.objects.link(mesh_obj) - - # Add shape keys - mesh_obj.shape_key_add(name="uv", from_mix=True) - mesh_obj.shape_key_add(name="model", from_mix=True) - mesh_obj.active_shape_key_index = 1 - - # Select - bpy.context.view_layer.objects.active = mesh_obj - mesh_obj.select_set( state = True, view_layer = None) - - bpy.ops.object.mode_set(mode='EDIT') - bm = bmesh.from_edit_mesh(mesh_obj.data) - - if hasattr(bm.faces, "ensure_lookup_table"): - bm.faces.ensure_lookup_table() - bm.verts.ensure_lookup_table() - - bm.edges.new((bm.verts[-4], bm.verts[-3])) - bm.edges.new((bm.verts[-3], bm.verts[-2])) - bm.edges.new((bm.verts[-2], bm.verts[-1])) - bm.edges.new((bm.verts[-1], bm.verts[-4])) - - - for i in range(len(m_verts_B)): - bm.verts[i].co = m_verts_B[i] - - - # Split concave faces to resolve issues with Shape deform - bpy.context.object.active_shape_key_index = 0 - bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE') - bpy.ops.mesh.select_all(action='SELECT') - bpy.ops.mesh.vert_connect_concave() - - - bpy.ops.object.mode_set(mode='OBJECT') - - - # Display as edges only - mesh_obj.show_wire = True - mesh_obj.show_all_edges = True - # mesh_obj.data.display_type = 'WIRE' #Esta linea deberia llevarte a la opcion wireframe - - + + mode = bpy.context.active_object.mode + + # Select + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.select_all(action='DESELECT') + obj.select_set( state = True, view_layer = None) + bpy.context.view_layer.objects.active = obj + + + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.select_mode(type="FACE") + bpy.context.scene.tool_settings.use_uv_select_sync = False + + + # Select all if OBJECT mode + if mode == 'OBJECT': + bpy.ops.mesh.select_all(action='SELECT') + # bpy.ops.uv.select_all(action='SELECT') + + # Create UV Map + if not obj.data.uv_layers: + if mode == 'OBJECT': + # Smart UV project + bpy.ops.uv.smart_project( + angle_limit=65, + island_margin=0.5, + user_area_weight=0, + use_aspect=True, + stretch_to_bounds=True + ) + elif mode == 'EDIT': + # Iron Faces + bpy.ops.uv.unwrap(method='ANGLE_BASED', margin=0) + bpy.ops.uv.textools_unwrap_faces_iron() + + + bm = bmesh.from_edit_mesh(obj.data) + uv_layers = bm.loops.layers.uv.verify() + + #Collect UV islands + bpy.ops.uv.select_all(action='SELECT') + islands = utilities_uv.getSelectionIslands(bm, uv_layers) + + # Collect clusters + uvs = {} + clusters = [] + uv_to_clusters = {} + vert_to_clusters = {} + + face_area_view = 0 + face_area_uv = 0 + + for face in bm.faces: + if face.select: + # Calculate triangle area for UV and View + # Triangle Verts + tri_uv = [loop[uv_layers].uv for loop in face.loops ] + tri_vt = [vert.co for vert in face.verts] + + #Triangle Areas + face_area_view += math.sqrt(utilities_texel.get_area_triangle( + tri_vt[0], + tri_vt[1], + tri_vt[2] + )) + face_area_uv += math.sqrt(utilities_texel.get_area_triangle( + tri_uv[0], + tri_uv[1], + tri_uv[2] + )) + + for i in range(len(face.loops)): + v = face.loops[i] + uv = Get_UVSet(uvs, bm, uv_layers, face.index, i) + + # # clusters + isMerged = False + for cluster in clusters: + d = (uv.pos() - cluster.uvs[0].pos()).length + if d <= 0.0000001: + #Merge + cluster.append(uv) + uv_to_clusters[uv] = cluster + if v not in vert_to_clusters: + vert_to_clusters[v] = cluster + isMerged = True; + break; + if not isMerged: + #New Group + clusters.append( UVCluster(v, [uv]) ) + uv_to_clusters[uv] = clusters[-1] + if v not in vert_to_clusters: + vert_to_clusters[v] = clusters[-1] + + scale = face_area_view / face_area_uv + + print("Scale {}x {} | {}".format(scale, face_area_view, face_area_uv)) + print("Islands {}x".format(len(islands))) + print("UV Vert Clusters {}x".format(len(clusters))) + + m_vert_cluster = [] + m_verts_org = [] + m_verts_A = [] + m_verts_B = [] + m_faces = [] + + for island in islands: + for face in island: + f = [] + for i in range(len(face.loops)): + v = face.loops[i].vert + uv = Get_UVSet(uvs, bm, uv_layers, face.index, i) + c = uv_to_clusters[ uv ] + + index = 0 + if c in m_vert_cluster: + index = m_vert_cluster.index(c) + + else: + index = len(m_vert_cluster) + m_vert_cluster.append(c) + m_verts_org.append(v) + + m_verts_A.append( Vector((uv.pos().x*scale - scale/2, uv.pos().y*scale -scale/2, 0)) ) + m_verts_B.append( obj.matrix_world @ v.co - bpy.context.scene.cursor.location ) + + f.append(index) + + m_faces.append(f) + + # Add UV bounds as edges + verts = [ + Vector((-scale/2, -scale/2, 0)), + Vector(( scale/2, -scale/2, 0)), + Vector(( scale/2, scale/2, 0)), + Vector((-scale/2, scale/2, 0)), + ] + m_verts_A = m_verts_A+verts; + m_verts_B = m_verts_B+verts; + + bpy.ops.object.mode_set(mode='OBJECT') + + # Create Mesh + mesh = bpy.data.meshes.new("mesh_texture") + mesh.from_pydata(m_verts_A, [], m_faces) + mesh.update() + mesh_obj = bpy.data.objects.new("UV_mesh {0}".format(obj.name), mesh) + mesh_obj.location = bpy.context.scene.cursor.location + bpy.context.collection.objects.link(mesh_obj) + + # Add shape keys + mesh_obj.shape_key_add(name="uv", from_mix=True) + mesh_obj.shape_key_add(name="model", from_mix=True) + mesh_obj.active_shape_key_index = 1 + + # Select + bpy.context.view_layer.objects.active = mesh_obj + mesh_obj.select_set( state = True, view_layer = None) + + bpy.ops.object.mode_set(mode='EDIT') + bm = bmesh.from_edit_mesh(mesh_obj.data) + + if hasattr(bm.faces, "ensure_lookup_table"): + bm.faces.ensure_lookup_table() + bm.verts.ensure_lookup_table() + + bm.edges.new((bm.verts[-4], bm.verts[-3])) + bm.edges.new((bm.verts[-3], bm.verts[-2])) + bm.edges.new((bm.verts[-2], bm.verts[-1])) + bm.edges.new((bm.verts[-1], bm.verts[-4])) + + + for i in range(len(m_verts_B)): + bm.verts[i].co = m_verts_B[i] + + + # Split concave faces to resolve issues with Shape deform + bpy.context.object.active_shape_key_index = 0 + bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE') + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.vert_connect_concave() + + + bpy.ops.object.mode_set(mode='OBJECT') + + + # Display as edges only + mesh_obj.show_wire = True + mesh_obj.show_all_edges = True + # mesh_obj.data.display_type = 'WIRE' #Esta linea deberia llevarte a la opcion wireframe + + - bpy.ops.object.select_all(action='DESELECT') - mesh_obj.select_set( state = True, view_layer = None) - bpy.context.view_layer.objects.active = mesh_obj + bpy.ops.object.select_all(action='DESELECT') + mesh_obj.select_set( state = True, view_layer = None) + bpy.context.view_layer.objects.active = mesh_obj def Get_UVSet(uvs, bm, layer, index_face, index_loop): - index = get_uv_index(index_face, index_loop) - if index not in uvs: - uvs[index] = UVSet(bm, layer, index_face, index_loop) + index = get_uv_index(index_face, index_loop) + if index not in uvs: + uvs[index] = UVSet(bm, layer, index_face, index_loop) - return uvs[index] + return uvs[index] class UVSet: - bm = None - layer = None - index_face = 0 - index_loop = 0 + bm = None + layer = None + index_face = 0 + index_loop = 0 - def __init__(self, bm, layer, index_face, index_loop): - self.bm = bm - self.layer = layer - self.index_face = index_face - self.index_loop = index_loop - - def uv(self): - face = self.bm.faces[self.index_face] - return face.loops[self.index_loop][self.layer] + def __init__(self, bm, layer, index_face, index_loop): + self.bm = bm + self.layer = layer + self.index_face = index_face + self.index_loop = index_loop + + def uv(self): + face = self.bm.faces[self.index_face] + return face.loops[self.index_loop][self.layer] - def pos(self): - return self.uv().uv + def pos(self): + return self.uv().uv - def vertex(self): - return face.loops[self.index_loop].vertex + def vertex(self): + return face.loops[self.index_loop].vertex def get_uv_index(index_face, index_loop): - return (index_face*1000000)+index_loop + return (index_face*1000000)+index_loop - + class UVCluster: - uvs = [] - vertex = None - - def __init__(self, vertex, uvs): - self.vertex = vertex - self.uvs = uvs - - def append(self, uv): - self.uvs.append(uv) + uvs = [] + vertex = None + + def __init__(self, vertex, uvs): + self.vertex = vertex + self.uvs = uvs + + def append(self, uv): + self.uvs.append(uv) bpy.utils.register_class(op) diff --git a/op_meshtex_pattern.py b/op_meshtex_pattern.py index ef8699b..41c919d 100644 --- a/op_meshtex_pattern.py +++ b/op_meshtex_pattern.py @@ -12,182 +12,182 @@ from . import utilities_ui class op(bpy.types.Operator): - bl_idname = "uv.textools_meshtex_pattern" - bl_label = "Create Pattern" - bl_description = "Create mesh pattern" - bl_options = {'REGISTER', 'UNDO'} + bl_idname = "uv.textools_meshtex_pattern" + bl_label = "Create Pattern" + bl_description = "Create mesh pattern" + bl_options = {'REGISTER', 'UNDO'} - mode : bpy.props.EnumProperty(items= - [('hexagon', 'Hexagons', ''), - ('triangle', 'Triangles', ''), - ('diamond', 'Diamonds', ''), - ('rectangle', 'Rectangles', ''), - ('stripe', 'Stripes', ''), - ('brick', 'Bricks', '')], - name = "Mode", - default = 'brick' - ) + mode : bpy.props.EnumProperty(items= + [('hexagon', 'Hexagons', ''), + ('triangle', 'Triangles', ''), + ('diamond', 'Diamonds', ''), + ('rectangle', 'Rectangles', ''), + ('stripe', 'Stripes', ''), + ('brick', 'Bricks', '')], + name = "Mode", + default = 'brick' + ) - size : bpy.props.IntProperty( - name = "Size", - description = "Size X and Y of the repetition", - default = 4, - min = 1, - max = 128 - ) + size : bpy.props.IntProperty( + name = "Size", + description = "Size X and Y of the repetition", + default = 4, + min = 1, + max = 128 + ) - scale : bpy.props.FloatProperty( - name = "Scale", - description = "Scale of the mesh pattern", - default = 1, - min = 0 - ) + scale : bpy.props.FloatProperty( + name = "Scale", + description = "Scale of the mesh pattern", + default = 1, + min = 0 + ) - @classmethod - def poll(cls, context): + @classmethod + def poll(cls, context): - if bpy.context.active_object and bpy.context.active_object.mode != 'OBJECT': - return False + if bpy.context.active_object and bpy.context.active_object.mode != 'OBJECT': + return False - return True + return True - def draw(self, context): - layout = self.layout - layout.prop(self, "mode") - layout.prop(self, "size") - layout.prop(self, "scale") + def draw(self, context): + layout = self.layout + layout.prop(self, "mode") + layout.prop(self, "size") + layout.prop(self, "scale") - def execute(self, context): - create_pattern(self, self.mode, self.size, self.scale) - return {'FINISHED'} + def execute(self, context): + create_pattern(self, self.mode, self.size, self.scale) + return {'FINISHED'} def AddArray(name, offset_x, offset_y, count): - modifier = bpy.context.object.modifiers.new(name=name, type='ARRAY') - # modifier = bpy.context.object.modifiers.new(name="{}_{}".format(name,count), type='ARRAY') - modifier.relative_offset_displace[0] = offset_x - modifier.relative_offset_displace[1] = offset_y - modifier.count = count - modifier.show_expanded = False - return modifier + modifier = bpy.context.object.modifiers.new(name=name, type='ARRAY') + # modifier = bpy.context.object.modifiers.new(name="{}_{}".format(name,count), type='ARRAY') + modifier.relative_offset_displace[0] = offset_x + modifier.relative_offset_displace[1] = offset_y + modifier.count = count + modifier.show_expanded = False + return modifier def create_pattern(self, mode, size, scale): - - print("Create pattern {}".format(mode)) - - # bpy.ops.object.mode_set(mode='OBJECT', toggle=False) - - context_override = None - if bpy.context.area.type != 'VIEW_3D': - context_override = utilities_ui.GetContextView3D() - if not context_override: - self.report({'ERROR_INVALID_INPUT'}, "This tool requires an available View3D view.") - return - - print("Mode '{}' size: '{}'".format(mode, size)) - - - if mode == 'hexagon': - bpy.ops.mesh.primitive_circle_add(vertices=6, radius=scale, fill_type='NGON') - - bpy.ops.object.mode_set(mode = 'EDIT') - if context_override: - bpy.ops.transform.rotate(context_override, value=math.pi*0.5, orient_axis='Z') - else: - bpy.ops.transform.rotate(value=math.pi*0.5, orient_axis='Z') - - bpy.ops.object.mode_set(mode = 'OBJECT') - - AddArray("Array0", 0.75,-0.5,2) - AddArray("Array1", 0,-0.66666666666,size) - AddArray("Array2", 1 - (0.5/3.5),0,size*0.66) - - elif mode == 'triangle': - bpy.ops.mesh.primitive_circle_add(vertices=3, radius=scale, fill_type='NGON') - - bpy.ops.object.mode_set(mode = 'EDIT') - - if context_override: - bpy.ops.transform.translate(context_override, value=(0, scale*0.5, 0), constraint_axis=(False, True, False)) - else: - bpy.ops.transform.translate(value=(0, scale*0.5, 0), constraint_axis=(False, True, False)) - - bpy.ops.object.mode_set(mode = 'OBJECT') - - modifier = bpy.context.object.modifiers.new(name="Mirror", type='MIRROR') - modifier.use_axis[0] = False - modifier.use_axis[1] = True - modifier.show_expanded = False - AddArray("Array0", 0.5,-0.5,2) - AddArray("Array1", 1-1/3.0,0,size) - AddArray("Array1", 0,-(1-1/3.0),size*0.66) - - elif mode == 'rectangle': - bpy.ops.mesh.primitive_plane_add(size=scale) - AddArray("Array0", 1,0,size) - AddArray("Array1", 0,-1,size) - - elif mode == 'diamond': - bpy.ops.mesh.primitive_plane_add(size=scale) - - bpy.ops.object.mode_set(mode = 'EDIT') - - if context_override: - bpy.ops.transform.rotate(context_override, value=math.pi*0.25, orient_axis='Z') - else: - bpy.ops.transform.rotate(value=math.pi*0.25, orient_axis='Z') - - bpy.ops.object.mode_set(mode = 'OBJECT') - - AddArray("Array0", 0.5,-0.5,2) - AddArray("Array1", 1-1/3,0,size) - AddArray("Array2", 0,-(1-1/3),size) - - elif mode == 'brick': - bpy.ops.mesh.primitive_plane_add(size=scale) - - bpy.ops.object.mode_set(mode = 'EDIT') - - if context_override: - bpy.ops.transform.resize(context_override, value=(1, 0.5, 1), constraint_axis=(True, True, False), orient_type='GLOBAL') - else: - bpy.ops.transform.resize(value=(1, 0.5, 1), constraint_axis=(True, True, False), orient_type='GLOBAL') - - bpy.ops.object.mode_set(mode = 'OBJECT') - - - AddArray("Array0", 0.5,-1,2) - AddArray("Array1", 1-(1/3),0,size) - AddArray("Array2", 0,-1,size) - - - elif mode == 'stripe': - bpy.ops.mesh.primitive_plane_add(size=1) - - bpy.ops.object.mode_set(mode = 'EDIT') - if context_override: - bpy.ops.transform.resize(context_override, value=(0.5, size/2, 1), constraint_axis=(True, True, False), orient_type='GLOBAL') - bpy.ops.transform.resize(context_override, value=(scale, scale, 1), constraint_axis=(True, True, False), orient_type='GLOBAL') - bpy.ops.transform.translate(context_override, value=(0, (-size/2)*scale, 0), constraint_axis=(False, True, False)) - else: - bpy.ops.transform.resize(value=(0.5, size/2, 1), constraint_axis=(True, True, False), orient_type='GLOBAL') - bpy.ops.transform.resize(value=(scale, scale, 1), constraint_axis=(True, True, False), orient_type='GLOBAL') - bpy.ops.transform.translate(value=(0, (-size/2)*scale, 0), constraint_axis=(False, True, False)) - - bpy.ops.object.mode_set(mode = 'OBJECT') - - AddArray("Array0", 1,0, size) - - # if bpy.context.object: - # bpy.context.object.name = "pattern_{}".format(mode) - # bpy.context.object.show_wire = True + + print("Create pattern {}".format(mode)) + + # bpy.ops.object.mode_set(mode='OBJECT', toggle=False) + + context_override = None + if bpy.context.area.type != 'VIEW_3D': + context_override = utilities_ui.GetContextView3D() + if not context_override: + self.report({'ERROR_INVALID_INPUT'}, "This tool requires an available View3D view.") + return + + print("Mode '{}' size: '{}'".format(mode, size)) + + + if mode == 'hexagon': + bpy.ops.mesh.primitive_circle_add(vertices=6, radius=scale, fill_type='NGON') + + bpy.ops.object.mode_set(mode = 'EDIT') + if context_override: + bpy.ops.transform.rotate(context_override, value=math.pi*0.5, orient_axis='Z') + else: + bpy.ops.transform.rotate(value=math.pi*0.5, orient_axis='Z') + + bpy.ops.object.mode_set(mode = 'OBJECT') + + AddArray("Array0", 0.75,-0.5,2) + AddArray("Array1", 0,-0.66666666666,size) + AddArray("Array2", 1 - (0.5/3.5),0,size*0.66) + + elif mode == 'triangle': + bpy.ops.mesh.primitive_circle_add(vertices=3, radius=scale, fill_type='NGON') + + bpy.ops.object.mode_set(mode = 'EDIT') + + if context_override: + bpy.ops.transform.translate(context_override, value=(0, scale*0.5, 0), constraint_axis=(False, True, False)) + else: + bpy.ops.transform.translate(value=(0, scale*0.5, 0), constraint_axis=(False, True, False)) + + bpy.ops.object.mode_set(mode = 'OBJECT') + + modifier = bpy.context.object.modifiers.new(name="Mirror", type='MIRROR') + modifier.use_axis[0] = False + modifier.use_axis[1] = True + modifier.show_expanded = False + AddArray("Array0", 0.5,-0.5,2) + AddArray("Array1", 1-1/3.0,0,size) + AddArray("Array1", 0,-(1-1/3.0),size*0.66) + + elif mode == 'rectangle': + bpy.ops.mesh.primitive_plane_add(size=scale) + AddArray("Array0", 1,0,size) + AddArray("Array1", 0,-1,size) + + elif mode == 'diamond': + bpy.ops.mesh.primitive_plane_add(size=scale) + + bpy.ops.object.mode_set(mode = 'EDIT') + + if context_override: + bpy.ops.transform.rotate(context_override, value=math.pi*0.25, orient_axis='Z') + else: + bpy.ops.transform.rotate(value=math.pi*0.25, orient_axis='Z') + + bpy.ops.object.mode_set(mode = 'OBJECT') + + AddArray("Array0", 0.5,-0.5,2) + AddArray("Array1", 1-1/3,0,size) + AddArray("Array2", 0,-(1-1/3),size) + + elif mode == 'brick': + bpy.ops.mesh.primitive_plane_add(size=scale) + + bpy.ops.object.mode_set(mode = 'EDIT') + + if context_override: + bpy.ops.transform.resize(context_override, value=(1, 0.5, 1), constraint_axis=(True, True, False), orient_type='GLOBAL') + else: + bpy.ops.transform.resize(value=(1, 0.5, 1), constraint_axis=(True, True, False), orient_type='GLOBAL') + + bpy.ops.object.mode_set(mode = 'OBJECT') + + + AddArray("Array0", 0.5,-1,2) + AddArray("Array1", 1-(1/3),0,size) + AddArray("Array2", 0,-1,size) + + + elif mode == 'stripe': + bpy.ops.mesh.primitive_plane_add(size=1) + + bpy.ops.object.mode_set(mode = 'EDIT') + if context_override: + bpy.ops.transform.resize(context_override, value=(0.5, size/2, 1), constraint_axis=(True, True, False), orient_type='GLOBAL') + bpy.ops.transform.resize(context_override, value=(scale, scale, 1), constraint_axis=(True, True, False), orient_type='GLOBAL') + bpy.ops.transform.translate(context_override, value=(0, (-size/2)*scale, 0), constraint_axis=(False, True, False)) + else: + bpy.ops.transform.resize(value=(0.5, size/2, 1), constraint_axis=(True, True, False), orient_type='GLOBAL') + bpy.ops.transform.resize(value=(scale, scale, 1), constraint_axis=(True, True, False), orient_type='GLOBAL') + bpy.ops.transform.translate(value=(0, (-size/2)*scale, 0), constraint_axis=(False, True, False)) + + bpy.ops.object.mode_set(mode = 'OBJECT') + + AddArray("Array0", 1,0, size) + + # if bpy.context.object: + # bpy.context.object.name = "pattern_{}".format(mode) + # bpy.context.object.show_wire = True bpy.utils.register_class(op) diff --git a/op_meshtex_trim.py b/op_meshtex_trim.py index 143c32e..1da1120 100644 --- a/op_meshtex_trim.py +++ b/op_meshtex_trim.py @@ -12,64 +12,64 @@ from . import utilities_meshtex class op(bpy.types.Operator): - bl_idname = "uv.textools_meshtex_trim" - bl_label = "Trim" - bl_description = "Trim Mesh Texture" - bl_options = {'REGISTER', 'UNDO'} + bl_idname = "uv.textools_meshtex_trim" + bl_label = "Trim" + bl_description = "Trim Mesh Texture" + bl_options = {'REGISTER', 'UNDO'} - @classmethod - def poll(cls, context): - if not bpy.context.active_object or bpy.context.active_object.mode != 'OBJECT': - return False - - if len(bpy.context.selected_objects) >= 1: - # Find a UV mesh - if utilities_meshtex.find_uv_mesh(bpy.context.selected_objects): - # Find 1 or more meshes to wrap - if len( utilities_meshtex.find_texture_meshes(bpy.context.selected_objects)) > 0: - return True + @classmethod + def poll(cls, context): + if not bpy.context.active_object or bpy.context.active_object.mode != 'OBJECT': + return False + + if len(bpy.context.selected_objects) >= 1: + # Find a UV mesh + if utilities_meshtex.find_uv_mesh(bpy.context.selected_objects): + # Find 1 or more meshes to wrap + if len( utilities_meshtex.find_texture_meshes(bpy.context.selected_objects)) > 0: + return True - return False + return False - def execute(self, context): - trim(self) - return {'FINISHED'} + def execute(self, context): + trim(self) + return {'FINISHED'} def trim(self): - # Wrap the mesh texture around the - print("Trim Mesh Texture :)") + # Wrap the mesh texture around the + print("Trim Mesh Texture :)") - # Collect UV mesh - obj_uv = utilities_meshtex.find_uv_mesh(bpy.context.selected_objects) - if not obj_uv: - self.report({'ERROR_INVALID_INPUT'}, "No UV mesh found" ) - return + # Collect UV mesh + obj_uv = utilities_meshtex.find_uv_mesh(bpy.context.selected_objects) + if not obj_uv: + self.report({'ERROR_INVALID_INPUT'}, "No UV mesh found" ) + return - # Collect texture meshes - obj_textures = utilities_meshtex.find_texture_meshes( bpy.context.selected_objects ) - + # Collect texture meshes + obj_textures = utilities_meshtex.find_texture_meshes( bpy.context.selected_objects ) + - if len(obj_textures) == 0: - self.report({'ERROR_INVALID_INPUT'}, "No meshes found for mesh textures" ) - return + if len(obj_textures) == 0: + self.report({'ERROR_INVALID_INPUT'}, "No meshes found for mesh textures" ) + return - # Setup Thickness - utilities_meshtex.uv_mesh_fit(obj_uv, obj_textures) + # Setup Thickness + utilities_meshtex.uv_mesh_fit(obj_uv, obj_textures) - # Apply bool modifier to trim - for obj in obj_textures: - name = "Trim UV" + # Apply bool modifier to trim + for obj in obj_textures: + name = "Trim UV" - if name in obj.modifiers: - obj.modifiers.remove( obj.modifiers[name] ) + if name in obj.modifiers: + obj.modifiers.remove( obj.modifiers[name] ) - modifier_bool = obj.modifiers.new(name=name, type='BOOLEAN') - modifier_bool.object = obj_uv + modifier_bool = obj.modifiers.new(name=name, type='BOOLEAN') + modifier_bool.object = obj_uv - bpy.ops.ui.textools_popup('INVOKE_DEFAULT', message="Collapse modifiers before wrapping") + bpy.ops.ui.textools_popup('INVOKE_DEFAULT', message="Collapse modifiers before wrapping") bpy.utils.register_class(op) diff --git a/op_meshtex_trim_collapse.py b/op_meshtex_trim_collapse.py index ae18418..5f61064 100644 --- a/op_meshtex_trim_collapse.py +++ b/op_meshtex_trim_collapse.py @@ -10,61 +10,61 @@ from . import utilities_meshtex def is_available(): - # If the selection contains a boolean modifier - obj_textures = utilities_meshtex.find_texture_meshes(bpy.context.selected_objects) - for obj in obj_textures: - for modifier in obj.modifiers: - if modifier.type == 'BOOLEAN': - return True - return False + # If the selection contains a boolean modifier + obj_textures = utilities_meshtex.find_texture_meshes(bpy.context.selected_objects) + for obj in obj_textures: + for modifier in obj.modifiers: + if modifier.type == 'BOOLEAN': + return True + return False class op(bpy.types.Operator): - bl_idname = "uv.textools_meshtex_trimcollapse" - bl_label = "Collapse" - bl_description = "Trim Mesh Texture" - bl_options = {'REGISTER', 'UNDO'} + bl_idname = "uv.textools_meshtex_trimcollapse" + bl_label = "Collapse" + bl_description = "Trim Mesh Texture" + bl_options = {'REGISTER', 'UNDO'} - @classmethod - def poll(cls, context): - if not bpy.context.active_object or bpy.context.active_object.mode != 'OBJECT': - return False - - return is_available() + @classmethod + def poll(cls, context): + if not bpy.context.active_object or bpy.context.active_object.mode != 'OBJECT': + return False + + return is_available() - def execute(self, context): - collapse(self) - return {'FINISHED'} + def execute(self, context): + collapse(self) + return {'FINISHED'} def collapse(self): - # Collect texture meshes - obj_textures = utilities_meshtex.find_texture_meshes( bpy.context.selected_objects ) - + # Collect texture meshes + obj_textures = utilities_meshtex.find_texture_meshes( bpy.context.selected_objects ) + - previous_selection = bpy.context.selected_objects.copy() - previous_active = bpy.context.view_layer.objects.active + previous_selection = bpy.context.selected_objects.copy() + previous_active = bpy.context.view_layer.objects.active - if len(obj_textures) == 0: - self.report({'ERROR_INVALID_INPUT'}, "No meshes found for mesh textures" ) - return + if len(obj_textures) == 0: + self.report({'ERROR_INVALID_INPUT'}, "No meshes found for mesh textures" ) + return - # Apply bool modifier to trim - for obj in obj_textures: - bpy.ops.object.select_all(action='DESELECT') - obj.select_set( state = True, view_layer = None) - bpy.context.view_layer.objects.active = obj - bpy.ops.object.convert(target='MESH') + # Apply bool modifier to trim + for obj in obj_textures: + bpy.ops.object.select_all(action='DESELECT') + obj.select_set( state = True, view_layer = None) + bpy.context.view_layer.objects.active = obj + bpy.ops.object.convert(target='MESH') - # restore selection - bpy.ops.object.select_all(action='DESELECT') - for obj in previous_selection: - obj.select_set( state = True, view_layer = None) - bpy.context.view_layer.objects.active = previous_active + # restore selection + bpy.ops.object.select_all(action='DESELECT') + for obj in previous_selection: + obj.select_set( state = True, view_layer = None) + bpy.context.view_layer.objects.active = previous_active - bpy.ops.ui.textools_popup('INVOKE_DEFAULT', message="{}x objects have been collapsed".format(len(obj_textures))) + bpy.ops.ui.textools_popup('INVOKE_DEFAULT', message="{}x objects have been collapsed".format(len(obj_textures))) bpy.utils.register_class(op) \ No newline at end of file diff --git a/op_meshtex_wrap.py b/op_meshtex_wrap.py index 523e4f8..e14780c 100644 --- a/op_meshtex_wrap.py +++ b/op_meshtex_wrap.py @@ -10,77 +10,77 @@ from . import utilities_meshtex class op(bpy.types.Operator): - bl_idname = "uv.textools_meshtex_wrap" - bl_label = "Wrap Mesh Texture" - bl_description = "Swap UV to XYZ coordinates" - bl_options = {'REGISTER', 'UNDO'} + bl_idname = "uv.textools_meshtex_wrap" + bl_label = "Wrap Mesh Texture" + bl_description = "Swap UV to XYZ coordinates" + bl_options = {'REGISTER', 'UNDO'} - @classmethod - def poll(cls, context): - if not bpy.context.active_object or bpy.context.active_object.mode != 'OBJECT': - return False - - # Wrap texture mesh around UV mesh - if len(bpy.context.selected_objects) >= 1: - # Find a UV mesh - if utilities_meshtex.find_uv_mesh(bpy.context.selected_objects): - # Find 1 or more meshes to wrap - if len( utilities_meshtex.find_texture_meshes(bpy.context.selected_objects)) > 0: - return True + @classmethod + def poll(cls, context): + if not bpy.context.active_object or bpy.context.active_object.mode != 'OBJECT': + return False + + # Wrap texture mesh around UV mesh + if len(bpy.context.selected_objects) >= 1: + # Find a UV mesh + if utilities_meshtex.find_uv_mesh(bpy.context.selected_objects): + # Find 1 or more meshes to wrap + if len( utilities_meshtex.find_texture_meshes(bpy.context.selected_objects)) > 0: + return True - return False + return False - def execute(self, context): - wrap_meshtex(self) - return {'FINISHED'} + def execute(self, context): + wrap_meshtex(self) + return {'FINISHED'} def wrap_meshtex(self): - # Wrap the mesh texture around the - print("Wrap Mesh Texture :)") - - # Collect UV mesh - obj_uv = utilities_meshtex.find_uv_mesh(bpy.context.selected_objects) - if not obj_uv: - self.report({'ERROR_INVALID_INPUT'}, "No UV mesh found" ) - return - - # Collect texture meshes - obj_textures = utilities_meshtex.find_texture_meshes( bpy.context.selected_objects ) - - if len(obj_textures) == 0: - self.report({'ERROR_INVALID_INPUT'}, "No meshes found for mesh textures" ) - return - - print("Wrap {} texture meshes".format(len(obj_textures))) - - # Undo wrapping - if bpy.context.scene.texToolsSettings.meshtexture_wrap > 0: - bpy.context.scene.texToolsSettings.meshtexture_wrap = 0 - # Clear modifiers - utilities_meshtex.uv_mesh_clear(obj_uv) - return - - # Setup Thickness - utilities_meshtex.uv_mesh_fit(obj_uv, obj_textures) - - for obj in obj_textures: - # Delete previous modifiers - for modifier in obj.modifiers: - if modifier.type == 'SURFACE_DEFORM': - obj.modifiers.remove(modifier) - break - - # Add mesh modifier - modifier_deform = obj.modifiers.new(name="SurfaceDeform", type='SURFACE_DEFORM') - modifier_deform.target = obj_uv - - obj.select_set( state = True, view_layer = None) - bpy.context.view_layer.objects.active = obj - bpy.ops.object.surfacedeform_bind(modifier="SurfaceDeform") - - # Apply wrapped morph state - bpy.context.scene.texToolsSettings.meshtexture_wrap = 1 + # Wrap the mesh texture around the + print("Wrap Mesh Texture :)") + + # Collect UV mesh + obj_uv = utilities_meshtex.find_uv_mesh(bpy.context.selected_objects) + if not obj_uv: + self.report({'ERROR_INVALID_INPUT'}, "No UV mesh found" ) + return + + # Collect texture meshes + obj_textures = utilities_meshtex.find_texture_meshes( bpy.context.selected_objects ) + + if len(obj_textures) == 0: + self.report({'ERROR_INVALID_INPUT'}, "No meshes found for mesh textures" ) + return + + print("Wrap {} texture meshes".format(len(obj_textures))) + + # Undo wrapping + if bpy.context.scene.texToolsSettings.meshtexture_wrap > 0: + bpy.context.scene.texToolsSettings.meshtexture_wrap = 0 + # Clear modifiers + utilities_meshtex.uv_mesh_clear(obj_uv) + return + + # Setup Thickness + utilities_meshtex.uv_mesh_fit(obj_uv, obj_textures) + + for obj in obj_textures: + # Delete previous modifiers + for modifier in obj.modifiers: + if modifier.type == 'SURFACE_DEFORM': + obj.modifiers.remove(modifier) + break + + # Add mesh modifier + modifier_deform = obj.modifiers.new(name="SurfaceDeform", type='SURFACE_DEFORM') + modifier_deform.target = obj_uv + + obj.select_set( state = True, view_layer = None) + bpy.context.view_layer.objects.active = obj + bpy.ops.object.surfacedeform_bind(modifier="SurfaceDeform") + + # Apply wrapped morph state + bpy.context.scene.texToolsSettings.meshtexture_wrap = 1 bpy.utils.register_class(op) diff --git a/op_rectify.py b/op_rectify.py index e5fc90a..600fdc0 100644 --- a/op_rectify.py +++ b/op_rectify.py @@ -11,32 +11,32 @@ from . import utilities_uv class op(bpy.types.Operator): - bl_idname = "uv.textools_rectify" - bl_label = "Rectify" - bl_description = "Align selected faces or verts to rectangular distribution." - bl_options = {'REGISTER', 'UNDO'} - - @classmethod - def poll(cls, context): - if not bpy.context.active_object: - return False + bl_idname = "uv.textools_rectify" + bl_label = "Rectify" + bl_description = "Align selected faces or verts to rectangular distribution." + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(cls, context): + if not bpy.context.active_object: + return False - if bpy.context.active_object.type != 'MESH': - return False + if bpy.context.active_object.type != 'MESH': + return False - if bpy.context.active_object.mode != 'EDIT': - return False + if bpy.context.active_object.mode != 'EDIT': + return False - # No Sync mode - if context.scene.tool_settings.use_uv_select_sync: - return False + # No Sync mode + if context.scene.tool_settings.use_uv_select_sync: + return False - return True - + return True + - def execute(self, context): - rectify(self, context) - return {'FINISHED'} + def execute(self, context): + rectify(self, context) + return {'FINISHED'} @@ -44,589 +44,589 @@ precision = 3 def rectify(self, context): - obj = bpy.context.active_object + obj = bpy.context.active_object - bm = bmesh.from_edit_mesh(obj.data) - uv_layers = bm.loops.layers.uv.verify() + bm = bmesh.from_edit_mesh(obj.data) + uv_layers = bm.loops.layers.uv.verify() - #Store selection - utilities_uv.selection_store() + #Store selection + utilities_uv.selection_store() - main(False) + main(False) - #Restore selection - utilities_uv.selection_restore() + #Restore selection + utilities_uv.selection_restore() def main(square = False, snapToClosest = False): - startTime = time.clock() - obj = bpy.context.active_object - me = obj.data - bm = bmesh.from_edit_mesh(me) - uv_layers = bm.loops.layers.uv.verify() - # bm.faces.layers.tex.verify() # currently blender needs both layers. - - face_act = bm.faces.active - targetFace = face_act - - #if len(bm.faces) > allowedFaces: - # operator.report({'ERROR'}, "selected more than " +str(allowedFaces) +" allowed faces.") - # return - - edgeVerts, filteredVerts, selFaces, nonQuadFaces, vertsDict, noEdge = ListsOfVerts(uv_layers, bm) - - if len(filteredVerts) is 0: return - if len(filteredVerts) is 1: - SnapCursorToClosestSelected(filteredVerts) - return - - cursorClosestTo = CursorClosestTo(filteredVerts) - #line is selected - - if len(selFaces) is 0: - if snapToClosest is True: - SnapCursorToClosestSelected(filteredVerts) - return - - VertsDictForLine(uv_layers, bm, filteredVerts, vertsDict) - - if AreVectsLinedOnAxis(filteredVerts) is False: - ScaleTo0OnAxisAndCursor(filteredVerts, vertsDict, cursorClosestTo) - return SuccessFinished(me, startTime) - - MakeEqualDistanceBetweenVertsInLine(filteredVerts, vertsDict, cursorClosestTo) - return SuccessFinished(me, startTime) - + startTime = time.clock() + obj = bpy.context.active_object + me = obj.data + bm = bmesh.from_edit_mesh(me) + uv_layers = bm.loops.layers.uv.verify() + # bm.faces.layers.tex.verify() # currently blender needs both layers. + + face_act = bm.faces.active + targetFace = face_act + + #if len(bm.faces) > allowedFaces: + # operator.report({'ERROR'}, "selected more than " +str(allowedFaces) +" allowed faces.") + # return + + edgeVerts, filteredVerts, selFaces, nonQuadFaces, vertsDict, noEdge = ListsOfVerts(uv_layers, bm) + + if len(filteredVerts) is 0: return + if len(filteredVerts) is 1: + SnapCursorToClosestSelected(filteredVerts) + return + + cursorClosestTo = CursorClosestTo(filteredVerts) + #line is selected + + if len(selFaces) is 0: + if snapToClosest is True: + SnapCursorToClosestSelected(filteredVerts) + return + + VertsDictForLine(uv_layers, bm, filteredVerts, vertsDict) + + if AreVectsLinedOnAxis(filteredVerts) is False: + ScaleTo0OnAxisAndCursor(filteredVerts, vertsDict, cursorClosestTo) + return SuccessFinished(me, startTime) + + MakeEqualDistanceBetweenVertsInLine(filteredVerts, vertsDict, cursorClosestTo) + return SuccessFinished(me, startTime) + - #else: - - #active face checks - if targetFace is None or targetFace.select is False or len(targetFace.verts) is not 4: - targetFace = selFaces[0] - else: - for l in targetFace.loops: - if l[uv_layers].select is False: - targetFace = selFaces[0] - break - - ShapeFace(uv_layers, operator, targetFace, vertsDict, square) - - for nf in nonQuadFaces: - for l in nf.loops: - luv = l[uv_layers] - luv.select = False - - if square: FollowActiveUV(operator, me, targetFace, selFaces, 'EVEN') - else: FollowActiveUV(operator, me, targetFace, selFaces) - - if noEdge is False: - #edge has ripped so we connect it back - for ev in edgeVerts: - key = (round(ev.uv.x, precision), round(ev.uv.y, precision)) - if key in vertsDict: - ev.uv = vertsDict[key][0].uv - ev.select = True - - return SuccessFinished(me, startTime) + #else: + + #active face checks + if targetFace is None or targetFace.select is False or len(targetFace.verts) is not 4: + targetFace = selFaces[0] + else: + for l in targetFace.loops: + if l[uv_layers].select is False: + targetFace = selFaces[0] + break + + ShapeFace(uv_layers, operator, targetFace, vertsDict, square) + + for nf in nonQuadFaces: + for l in nf.loops: + luv = l[uv_layers] + luv.select = False + + if square: FollowActiveUV(operator, me, targetFace, selFaces, 'EVEN') + else: FollowActiveUV(operator, me, targetFace, selFaces) + + if noEdge is False: + #edge has ripped so we connect it back + for ev in edgeVerts: + key = (round(ev.uv.x, precision), round(ev.uv.y, precision)) + if key in vertsDict: + ev.uv = vertsDict[key][0].uv + ev.select = True + + return SuccessFinished(me, startTime) def ListsOfVerts(uv_layers, bm): - edgeVerts = [] - allEdgeVerts = [] - filteredVerts = [] - selFaces = [] - nonQuadFaces = [] - vertsDict = defaultdict(list) #dict - - for f in bm.faces: - isFaceSel = True - facesEdgeVerts = [] - if (f.select == False): - continue - - #collect edge verts if any - for l in f.loops: - luv = l[uv_layers] - if luv.select is True: - facesEdgeVerts.append(luv) - else: isFaceSel = False - - allEdgeVerts.extend(facesEdgeVerts) - if isFaceSel: - if len(f.verts) is not 4: - nonQuadFaces.append(f) - edgeVerts.extend(facesEdgeVerts) - else: - selFaces.append(f) - - for l in f.loops: - luv = l[uv_layers] - x = round(luv.uv.x, precision) - y = round(luv.uv.y, precision) - vertsDict[(x, y)].append(luv) - - else: edgeVerts.extend(facesEdgeVerts) - - noEdge = False - if len(edgeVerts) is 0: - noEdge = True - edgeVerts.extend(allEdgeVerts) - - if len(selFaces) is 0: - for ev in edgeVerts: - if ListQuasiContainsVect(filteredVerts, ev) is False: - filteredVerts.append(ev) - else: filteredVerts = edgeVerts - - return edgeVerts, filteredVerts, selFaces, nonQuadFaces, vertsDict, noEdge + edgeVerts = [] + allEdgeVerts = [] + filteredVerts = [] + selFaces = [] + nonQuadFaces = [] + vertsDict = defaultdict(list) #dict + + for f in bm.faces: + isFaceSel = True + facesEdgeVerts = [] + if (f.select == False): + continue + + #collect edge verts if any + for l in f.loops: + luv = l[uv_layers] + if luv.select is True: + facesEdgeVerts.append(luv) + else: isFaceSel = False + + allEdgeVerts.extend(facesEdgeVerts) + if isFaceSel: + if len(f.verts) is not 4: + nonQuadFaces.append(f) + edgeVerts.extend(facesEdgeVerts) + else: + selFaces.append(f) + + for l in f.loops: + luv = l[uv_layers] + x = round(luv.uv.x, precision) + y = round(luv.uv.y, precision) + vertsDict[(x, y)].append(luv) + + else: edgeVerts.extend(facesEdgeVerts) + + noEdge = False + if len(edgeVerts) is 0: + noEdge = True + edgeVerts.extend(allEdgeVerts) + + if len(selFaces) is 0: + for ev in edgeVerts: + if ListQuasiContainsVect(filteredVerts, ev) is False: + filteredVerts.append(ev) + else: filteredVerts = edgeVerts + + return edgeVerts, filteredVerts, selFaces, nonQuadFaces, vertsDict, noEdge def ListQuasiContainsVect(list, vect): - for v in list: - if AreVertsQuasiEqual(v, vect): - return True - return False + for v in list: + if AreVertsQuasiEqual(v, vect): + return True + return False def SnapCursorToClosestSelected(filteredVerts): - #TODO: snap to closest selected - if len(filteredVerts) is 1: - SetAll2dCursorsTo(filteredVerts[0].uv.x, filteredVerts[0].uv.y) - - return + #TODO: snap to closest selected + if len(filteredVerts) is 1: + SetAll2dCursorsTo(filteredVerts[0].uv.x, filteredVerts[0].uv.y) + + return def VertsDictForLine(uv_layers, bm, selVerts, vertsDict): - for f in bm.faces: - for l in f.loops: - luv = l[uv_layers] - if luv.select is True: - x = round(luv.uv.x, precision) - y = round(luv.uv.y, precision) - - vertsDict[(x, y)].append(luv) + for f in bm.faces: + for l in f.loops: + luv = l[uv_layers] + if luv.select is True: + x = round(luv.uv.x, precision) + y = round(luv.uv.y, precision) + + vertsDict[(x, y)].append(luv) def AreVectsLinedOnAxis(verts): - areLinedX = True - areLinedY = True - allowedError = 0.0009 - valX = verts[0].uv.x - valY = verts[0].uv.y - for v in verts: - if abs(valX - v.uv.x) > allowedError: - areLinedX = False - if abs(valY - v.uv.y) > allowedError: - areLinedY = False - return areLinedX or areLinedY + areLinedX = True + areLinedY = True + allowedError = 0.0009 + valX = verts[0].uv.x + valY = verts[0].uv.y + for v in verts: + if abs(valX - v.uv.x) > allowedError: + areLinedX = False + if abs(valY - v.uv.y) > allowedError: + areLinedY = False + return areLinedX or areLinedY def ScaleTo0OnAxisAndCursor(filteredVerts, vertsDict, startv = None, horizontal = None): - - verts = filteredVerts - verts.sort(key=lambda x: x.uv[0]) #sort by .x - - first = verts[0] - last = verts[len(verts)-1] - - if horizontal is None: - horizontal = True - if ((last.uv.x - first.uv.x) >0.0009): - slope = (last.uv.y - first.uv.y)/(last.uv.x - first.uv.x) - if (slope > 1) or (slope <-1): - horizontal = False - else: - horizontal = False - - if horizontal is True: - if startv is None: - startv = first - - SetAll2dCursorsTo(startv.uv.x, startv.uv.y) - #scale to 0 on Y - ScaleTo0('Y') - return - - else: - verts.sort(key=lambda x: x.uv[1]) #sort by .y - verts.reverse() #reverse because y values drop from up to down - first = verts[0] - last = verts[len(verts)-1] - if startv is None: - startv = first - - SetAll2dCursorsTo(startv.uv.x, startv.uv.y) - #scale to 0 on X - ScaleTo0('X') - return + + verts = filteredVerts + verts.sort(key=lambda x: x.uv[0]) #sort by .x + + first = verts[0] + last = verts[len(verts)-1] + + if horizontal is None: + horizontal = True + if ((last.uv.x - first.uv.x) >0.0009): + slope = (last.uv.y - first.uv.y)/(last.uv.x - first.uv.x) + if (slope > 1) or (slope <-1): + horizontal = False + else: + horizontal = False + + if horizontal is True: + if startv is None: + startv = first + + SetAll2dCursorsTo(startv.uv.x, startv.uv.y) + #scale to 0 on Y + ScaleTo0('Y') + return + + else: + verts.sort(key=lambda x: x.uv[1]) #sort by .y + verts.reverse() #reverse because y values drop from up to down + first = verts[0] + last = verts[len(verts)-1] + if startv is None: + startv = first + + SetAll2dCursorsTo(startv.uv.x, startv.uv.y) + #scale to 0 on X + ScaleTo0('X') + return def SetAll2dCursorsTo(x,y): - last_area = bpy.context.area.type - bpy.context.area.type = 'IMAGE_EDITOR' + last_area = bpy.context.area.type + bpy.context.area.type = 'IMAGE_EDITOR' - bpy.ops.uv.cursor_set(location=(x, y)) + bpy.ops.uv.cursor_set(location=(x, y)) - bpy.context.area.type = last_area - return + bpy.context.area.type = last_area + return def CursorClosestTo(verts, allowedError = 0.025): - ratioX, ratioY = ImageRatio() - - #any length that is certantly not smaller than distance of the closest - min = 1000 - minV = verts[0] - for v in verts: - if v is None: continue - for area in bpy.context.screen.areas: - if area.type == 'IMAGE_EDITOR': - loc = area.spaces[0].cursor_location - hyp = hypot(loc.x/ratioX -v.uv.x, loc.y/ratioY -v.uv.y) - if (hyp < min): - min = hyp - minV = v - - if min is not 1000: - return minV - return None + ratioX, ratioY = ImageRatio() + + #any length that is certantly not smaller than distance of the closest + min = 1000 + minV = verts[0] + for v in verts: + if v is None: continue + for area in bpy.context.screen.areas: + if area.type == 'IMAGE_EDITOR': + loc = area.spaces[0].cursor_location + hyp = hypot(loc.x/ratioX -v.uv.x, loc.y/ratioY -v.uv.y) + if (hyp < min): + min = hyp + minV = v + + if min is not 1000: + return minV + return None def SuccessFinished(me, startTime): - #use for backtrack of steps - #bpy.ops.ed.undo_push() - bmesh.update_edit_mesh(me) - #elapsed = round(time.clock()-startTime, 2) - #if (elapsed >= 0.05): operator.report({'INFO'}, "UvSquares finished, elapsed:", elapsed, "s.") - return + #use for backtrack of steps + #bpy.ops.ed.undo_push() + bmesh.update_edit_mesh(me) + #elapsed = round(time.clock()-startTime, 2) + #if (elapsed >= 0.05): operator.report({'INFO'}, "UvSquares finished, elapsed:", elapsed, "s.") + return def ShapeFace(uv_layers, operator, targetFace, vertsDict, square): - corners = [] - for l in targetFace.loops: - luv = l[uv_layers] - corners.append(luv) - - if len(corners) is not 4: - #operator.report({'ERROR'}, "bla") - return - - lucv, ldcv, rucv, rdcv = Corners(corners) - - cct = CursorClosestTo([lucv, ldcv, rdcv, rucv]) - if cct is None: - cct = lucv - - MakeUvFaceEqualRectangle(vertsDict, lucv, rucv, rdcv, ldcv, cct, square) - return + corners = [] + for l in targetFace.loops: + luv = l[uv_layers] + corners.append(luv) + + if len(corners) is not 4: + #operator.report({'ERROR'}, "bla") + return + + lucv, ldcv, rucv, rdcv = Corners(corners) + + cct = CursorClosestTo([lucv, ldcv, rdcv, rucv]) + if cct is None: + cct = lucv + + MakeUvFaceEqualRectangle(vertsDict, lucv, rucv, rdcv, ldcv, cct, square) + return def MakeUvFaceEqualRectangle(vertsDict, lucv, rucv, rdcv, ldcv, startv, square = False): - ratioX, ratioY = ImageRatio() - ratio = ratioX/ratioY - - if startv is None: startv = lucv.uv - elif AreVertsQuasiEqual(startv, rucv): startv = rucv.uv - elif AreVertsQuasiEqual(startv, rdcv): startv = rdcv.uv - elif AreVertsQuasiEqual(startv, ldcv): startv = ldcv.uv - else: startv = lucv.uv - - lucv = lucv.uv - rucv = rucv.uv - rdcv = rdcv.uv - ldcv = ldcv.uv + ratioX, ratioY = ImageRatio() + ratio = ratioX/ratioY + + if startv is None: startv = lucv.uv + elif AreVertsQuasiEqual(startv, rucv): startv = rucv.uv + elif AreVertsQuasiEqual(startv, rdcv): startv = rdcv.uv + elif AreVertsQuasiEqual(startv, ldcv): startv = ldcv.uv + else: startv = lucv.uv + + lucv = lucv.uv + rucv = rucv.uv + rdcv = rdcv.uv + ldcv = ldcv.uv - if (startv == lucv): - finalScaleX = hypotVert(lucv, rucv) - finalScaleY = hypotVert(lucv, ldcv) - currRowX = lucv.x - currRowY = lucv.y - - elif (startv == rucv): - finalScaleX = hypotVert(rucv, lucv) - finalScaleY = hypotVert(rucv, rdcv) - currRowX = rucv.x - finalScaleX - currRowY = rucv.y - - elif (startv == rdcv): - finalScaleX = hypotVert(rdcv, ldcv) - finalScaleY = hypotVert(rdcv, rucv) - currRowX = rdcv.x - finalScaleX - currRowY = rdcv.y + finalScaleY - - else: - finalScaleX = hypotVert(ldcv, rdcv) - finalScaleY = hypotVert(ldcv, lucv) - currRowX = ldcv.x - currRowY = ldcv.y +finalScaleY - - if square: finalScaleY = finalScaleX*ratio - #lucv, rucv - x = round(lucv.x, precision) - y = round(lucv.y, precision) - for v in vertsDict[(x,y)]: - v.uv.x = currRowX - v.uv.y = currRowY + if (startv == lucv): + finalScaleX = hypotVert(lucv, rucv) + finalScaleY = hypotVert(lucv, ldcv) + currRowX = lucv.x + currRowY = lucv.y + + elif (startv == rucv): + finalScaleX = hypotVert(rucv, lucv) + finalScaleY = hypotVert(rucv, rdcv) + currRowX = rucv.x - finalScaleX + currRowY = rucv.y + + elif (startv == rdcv): + finalScaleX = hypotVert(rdcv, ldcv) + finalScaleY = hypotVert(rdcv, rucv) + currRowX = rdcv.x - finalScaleX + currRowY = rdcv.y + finalScaleY + + else: + finalScaleX = hypotVert(ldcv, rdcv) + finalScaleY = hypotVert(ldcv, lucv) + currRowX = ldcv.x + currRowY = ldcv.y +finalScaleY + + if square: finalScaleY = finalScaleX*ratio + #lucv, rucv + x = round(lucv.x, precision) + y = round(lucv.y, precision) + for v in vertsDict[(x,y)]: + v.uv.x = currRowX + v.uv.y = currRowY - x = round(rucv.x, precision) - y = round(rucv.y, precision) - for v in vertsDict[(x,y)]: - v.uv.x = currRowX + finalScaleX - v.uv.y = currRowY - - #rdcv, ldcv - x = round(rdcv.x, precision) - y = round(rdcv.y, precision) - for v in vertsDict[(x,y)]: - v.uv.x = currRowX + finalScaleX - v.uv.y = currRowY - finalScaleY - - x = round(ldcv.x, precision) - y = round(ldcv.y, precision) - for v in vertsDict[(x,y)]: - v.uv.x = currRowX - v.uv.y = currRowY - finalScaleY - - - return + x = round(rucv.x, precision) + y = round(rucv.y, precision) + for v in vertsDict[(x,y)]: + v.uv.x = currRowX + finalScaleX + v.uv.y = currRowY + + #rdcv, ldcv + x = round(rdcv.x, precision) + y = round(rdcv.y, precision) + for v in vertsDict[(x,y)]: + v.uv.x = currRowX + finalScaleX + v.uv.y = currRowY - finalScaleY + + x = round(ldcv.x, precision) + y = round(ldcv.y, precision) + for v in vertsDict[(x,y)]: + v.uv.x = currRowX + v.uv.y = currRowY - finalScaleY + + + return def FollowActiveUV(operator, me, f_act, faces, EXTEND_MODE = 'LENGTH_AVERAGE'): - bm = bmesh.from_edit_mesh(me) - uv_act = bm.loops.layers.uv.active - - # our own local walker - def walk_face_init(faces, f_act): - # first tag all faces True (so we dont uvmap them) - for f in bm.faces: - f.tag = True - # then tag faces arg False - for f in faces: - f.tag = False - # tag the active face True since we begin there - f_act.tag = True - - def walk_face(f): - # all faces in this list must be tagged - f.tag = True - faces_a = [f] - faces_b = [] - - while faces_a: - for f in faces_a: - for l in f.loops: - l_edge = l.edge - if (l_edge.is_manifold is True) and (l_edge.seam is False): - l_other = l.link_loop_radial_next - f_other = l_other.face - if not f_other.tag: - yield (f, l, f_other) - f_other.tag = True - faces_b.append(f_other) - # swap - faces_a, faces_b = faces_b, faces_a - faces_b.clear() - - def walk_edgeloop(l): - """ - Could make this a generic function - """ - e_first = l.edge - e = None - while True: - e = l.edge - yield e - - # don't step past non-manifold edges - if e.is_manifold: - # welk around the quad and then onto the next face - l = l.link_loop_radial_next - if len(l.face.verts) == 4: - l = l.link_loop_next.link_loop_next - if l.edge is e_first: - break - else: - break - else: - break - - def extrapolate_uv(fac, - l_a_outer, l_a_inner, - l_b_outer, l_b_inner): - l_b_inner[:] = l_a_inner - l_b_outer[:] = l_a_inner + ((l_a_inner - l_a_outer) * fac) - - def apply_uv(f_prev, l_prev, f_next): - l_a = [None, None, None, None] - l_b = [None, None, None, None] - - l_a[0] = l_prev - l_a[1] = l_a[0].link_loop_next - l_a[2] = l_a[1].link_loop_next - l_a[3] = l_a[2].link_loop_next - - # l_b - # +-----------+ - # |(3) |(2) - # | | - # |l_next(0) |(1) - # +-----------+ - # ^ - # l_a | - # +-----------+ - # |l_prev(0) |(1) - # | (f) | - # |(3) |(2) - # +-----------+ - # copy from this face to the one above. - - # get the other loops - l_next = l_prev.link_loop_radial_next - if l_next.vert != l_prev.vert: - l_b[1] = l_next - l_b[0] = l_b[1].link_loop_next - l_b[3] = l_b[0].link_loop_next - l_b[2] = l_b[3].link_loop_next - else: - l_b[0] = l_next - l_b[1] = l_b[0].link_loop_next - l_b[2] = l_b[1].link_loop_next - l_b[3] = l_b[2].link_loop_next - - l_a_uv = [l[uv_act].uv for l in l_a] - l_b_uv = [l[uv_act].uv for l in l_b] - - if EXTEND_MODE == 'LENGTH_AVERAGE': - fac = edge_lengths[l_b[2].edge.index][0] / edge_lengths[l_a[1].edge.index][0] - elif EXTEND_MODE == 'LENGTH': - a0, b0, c0 = l_a[3].vert.co, l_a[0].vert.co, l_b[3].vert.co - a1, b1, c1 = l_a[2].vert.co, l_a[1].vert.co, l_b[2].vert.co - - d1 = (a0 - b0).length + (a1 - b1).length - d2 = (b0 - c0).length + (b1 - c1).length - try: - fac = d2 / d1 - except ZeroDivisionError: - fac = 1.0 - else: - fac = 1.0 - - extrapolate_uv(fac, - l_a_uv[3], l_a_uv[0], - l_b_uv[3], l_b_uv[0]) - - extrapolate_uv(fac, - l_a_uv[2], l_a_uv[1], - l_b_uv[2], l_b_uv[1]) - - # ------------------------------------------- - # Calculate average length per loop if needed - - if EXTEND_MODE == 'LENGTH_AVERAGE': - bm.edges.index_update() - edge_lengths = [None] * len(bm.edges) #NoneType times the length of edges list - - for f in faces: - # we know its a quad - l_quad = f.loops[:] - l_pair_a = (l_quad[0], l_quad[2]) - l_pair_b = (l_quad[1], l_quad[3]) - - for l_pair in (l_pair_a, l_pair_b): - if edge_lengths[l_pair[0].edge.index] is None: - - edge_length_store = [-1.0] - edge_length_accum = 0.0 - edge_length_total = 0 - - for l in l_pair: - if edge_lengths[l.edge.index] is None: - for e in walk_edgeloop(l): - if edge_lengths[e.index] is None: - edge_lengths[e.index] = edge_length_store - edge_length_accum += e.calc_length() - edge_length_total += 1 - - edge_length_store[0] = edge_length_accum / edge_length_total - - # done with average length - # ------------------------ - - walk_face_init(faces, f_act) - for f_triple in walk_face(f_act): - apply_uv(*f_triple) - - bmesh.update_edit_mesh(me, False) + bm = bmesh.from_edit_mesh(me) + uv_act = bm.loops.layers.uv.active + + # our own local walker + def walk_face_init(faces, f_act): + # first tag all faces True (so we dont uvmap them) + for f in bm.faces: + f.tag = True + # then tag faces arg False + for f in faces: + f.tag = False + # tag the active face True since we begin there + f_act.tag = True + + def walk_face(f): + # all faces in this list must be tagged + f.tag = True + faces_a = [f] + faces_b = [] + + while faces_a: + for f in faces_a: + for l in f.loops: + l_edge = l.edge + if (l_edge.is_manifold is True) and (l_edge.seam is False): + l_other = l.link_loop_radial_next + f_other = l_other.face + if not f_other.tag: + yield (f, l, f_other) + f_other.tag = True + faces_b.append(f_other) + # swap + faces_a, faces_b = faces_b, faces_a + faces_b.clear() + + def walk_edgeloop(l): + """ + Could make this a generic function + """ + e_first = l.edge + e = None + while True: + e = l.edge + yield e + + # don't step past non-manifold edges + if e.is_manifold: + # welk around the quad and then onto the next face + l = l.link_loop_radial_next + if len(l.face.verts) == 4: + l = l.link_loop_next.link_loop_next + if l.edge is e_first: + break + else: + break + else: + break + + def extrapolate_uv(fac, + l_a_outer, l_a_inner, + l_b_outer, l_b_inner): + l_b_inner[:] = l_a_inner + l_b_outer[:] = l_a_inner + ((l_a_inner - l_a_outer) * fac) + + def apply_uv(f_prev, l_prev, f_next): + l_a = [None, None, None, None] + l_b = [None, None, None, None] + + l_a[0] = l_prev + l_a[1] = l_a[0].link_loop_next + l_a[2] = l_a[1].link_loop_next + l_a[3] = l_a[2].link_loop_next + + # l_b + # +-----------+ + # |(3) |(2) + # | | + # |l_next(0) |(1) + # +-----------+ + # ^ + # l_a | + # +-----------+ + # |l_prev(0) |(1) + # | (f) | + # |(3) |(2) + # +-----------+ + # copy from this face to the one above. + + # get the other loops + l_next = l_prev.link_loop_radial_next + if l_next.vert != l_prev.vert: + l_b[1] = l_next + l_b[0] = l_b[1].link_loop_next + l_b[3] = l_b[0].link_loop_next + l_b[2] = l_b[3].link_loop_next + else: + l_b[0] = l_next + l_b[1] = l_b[0].link_loop_next + l_b[2] = l_b[1].link_loop_next + l_b[3] = l_b[2].link_loop_next + + l_a_uv = [l[uv_act].uv for l in l_a] + l_b_uv = [l[uv_act].uv for l in l_b] + + if EXTEND_MODE == 'LENGTH_AVERAGE': + fac = edge_lengths[l_b[2].edge.index][0] / edge_lengths[l_a[1].edge.index][0] + elif EXTEND_MODE == 'LENGTH': + a0, b0, c0 = l_a[3].vert.co, l_a[0].vert.co, l_b[3].vert.co + a1, b1, c1 = l_a[2].vert.co, l_a[1].vert.co, l_b[2].vert.co + + d1 = (a0 - b0).length + (a1 - b1).length + d2 = (b0 - c0).length + (b1 - c1).length + try: + fac = d2 / d1 + except ZeroDivisionError: + fac = 1.0 + else: + fac = 1.0 + + extrapolate_uv(fac, + l_a_uv[3], l_a_uv[0], + l_b_uv[3], l_b_uv[0]) + + extrapolate_uv(fac, + l_a_uv[2], l_a_uv[1], + l_b_uv[2], l_b_uv[1]) + + # ------------------------------------------- + # Calculate average length per loop if needed + + if EXTEND_MODE == 'LENGTH_AVERAGE': + bm.edges.index_update() + edge_lengths = [None] * len(bm.edges) #NoneType times the length of edges list + + for f in faces: + # we know its a quad + l_quad = f.loops[:] + l_pair_a = (l_quad[0], l_quad[2]) + l_pair_b = (l_quad[1], l_quad[3]) + + for l_pair in (l_pair_a, l_pair_b): + if edge_lengths[l_pair[0].edge.index] is None: + + edge_length_store = [-1.0] + edge_length_accum = 0.0 + edge_length_total = 0 + + for l in l_pair: + if edge_lengths[l.edge.index] is None: + for e in walk_edgeloop(l): + if edge_lengths[e.index] is None: + edge_lengths[e.index] = edge_length_store + edge_length_accum += e.calc_length() + edge_length_total += 1 + + edge_length_store[0] = edge_length_accum / edge_length_total + + # done with average length + # ------------------------ + + walk_face_init(faces, f_act) + for f_triple in walk_face(f_act): + apply_uv(*f_triple) + + bmesh.update_edit_mesh(me, False) def ImageRatio(): - ratioX, ratioY = 256,256 - for a in bpy.context.screen.areas: - if a.type == 'IMAGE_EDITOR': - img = a.spaces[0].image - if img is not None and img.size[0] is not 0: - ratioX, ratioY = img.size[0], img.size[1] - break - return ratioX, ratioY + ratioX, ratioY = 256,256 + for a in bpy.context.screen.areas: + if a.type == 'IMAGE_EDITOR': + img = a.spaces[0].image + if img is not None and img.size[0] is not 0: + ratioX, ratioY = img.size[0], img.size[1] + break + return ratioX, ratioY def Corners(corners): - firstHighest = corners[0] - for c in corners: - if c.uv.y > firstHighest.uv.y: - firstHighest = c - corners.remove(firstHighest) - - secondHighest = corners[0] - for c in corners: - if (c.uv.y > secondHighest.uv.y): - secondHighest = c - - if firstHighest.uv.x < secondHighest.uv.x: - leftUp = firstHighest - rightUp = secondHighest - else: - leftUp = secondHighest - rightUp = firstHighest - corners.remove(secondHighest) - - firstLowest = corners[0] - secondLowest = corners[1] - - if firstLowest.uv.x < secondLowest.uv.x: - leftDown = firstLowest - rightDown = secondLowest - else: - leftDown = secondLowest - rightDown = firstLowest - - return leftUp, leftDown, rightUp, rightDown + firstHighest = corners[0] + for c in corners: + if c.uv.y > firstHighest.uv.y: + firstHighest = c + corners.remove(firstHighest) + + secondHighest = corners[0] + for c in corners: + if (c.uv.y > secondHighest.uv.y): + secondHighest = c + + if firstHighest.uv.x < secondHighest.uv.x: + leftUp = firstHighest + rightUp = secondHighest + else: + leftUp = secondHighest + rightUp = firstHighest + corners.remove(secondHighest) + + firstLowest = corners[0] + secondLowest = corners[1] + + if firstLowest.uv.x < secondLowest.uv.x: + leftDown = firstLowest + rightDown = secondLowest + else: + leftDown = secondLowest + rightDown = firstLowest + + return leftUp, leftDown, rightUp, rightDown def AreVertsQuasiEqual(v1, v2, allowedError = 0.0009): - if abs(v1.uv.x -v2.uv.x) < allowedError and abs(v1.uv.y -v2.uv.y) < allowedError: - return True - return False + if abs(v1.uv.x -v2.uv.x) < allowedError and abs(v1.uv.y -v2.uv.y) < allowedError: + return True + return False diff --git a/op_select_islands_flipped.py b/op_select_islands_flipped.py index 7ff38d5..cb36262 100644 --- a/op_select_islands_flipped.py +++ b/op_select_islands_flipped.py @@ -10,124 +10,124 @@ import imp imp.reload(utilities_uv) class op(bpy.types.Operator): - bl_idname = "uv.textools_select_islands_flipped" - bl_label = "Select Flipped" - bl_description = "Select all flipped UV islands" - bl_options = {'REGISTER', 'UNDO'} + bl_idname = "uv.textools_select_islands_flipped" + bl_label = "Select Flipped" + bl_description = "Select all flipped UV islands" + bl_options = {'REGISTER', 'UNDO'} - @classmethod - def poll(cls, context): - if not bpy.context.active_object: - return False + @classmethod + def poll(cls, context): + if not bpy.context.active_object: + return False - if bpy.context.active_object.type != 'MESH': - return False + if bpy.context.active_object.type != 'MESH': + return False - #Only in Edit mode - if bpy.context.active_object.mode != 'EDIT': - return False + #Only in Edit mode + if bpy.context.active_object.mode != 'EDIT': + return False - #Only in UV editor mode - if bpy.context.area.type != 'IMAGE_EDITOR': - return False + #Only in UV editor mode + if bpy.context.area.type != 'IMAGE_EDITOR': + return False - ##Requires UV map - if not bpy.context.object.data.uv_layers: - return False + ##Requires UV map + if not bpy.context.object.data.uv_layers: + return False - #Not in Synced mode - if bpy.context.scene.tool_settings.use_uv_select_sync: - return False - - return True + #Not in Synced mode + if bpy.context.scene.tool_settings.use_uv_select_sync: + return False + + return True - def execute(self, context): - - select_flipped(context) - return {'FINISHED'} + def execute(self, context): + + select_flipped(context) + return {'FINISHED'} def select_flipped(context): - bm = bmesh.from_edit_mesh(bpy.context.active_object.data) - uv_layers = bm.loops.layers.uv.verify() + bm = bmesh.from_edit_mesh(bpy.context.active_object.data) + uv_layers = bm.loops.layers.uv.verify() - bpy.context.scene.tool_settings.uv_select_mode = 'FACE' - bpy.ops.uv.select_all(action='SELECT') + bpy.context.scene.tool_settings.uv_select_mode = 'FACE' + bpy.ops.uv.select_all(action='SELECT') - islands = utilities_uv.getSelectionIslands() - + islands = utilities_uv.getSelectionIslands() + - bpy.context.scene.tool_settings.uv_select_mode = 'FACE' - bpy.context.scene.tool_settings.use_uv_select_sync = False - bpy.ops.uv.select_all(action='DESELECT') + bpy.context.scene.tool_settings.uv_select_mode = 'FACE' + bpy.context.scene.tool_settings.use_uv_select_sync = False + bpy.ops.uv.select_all(action='DESELECT') - for island in islands: + for island in islands: - is_flipped = False - for face in island: - if is_flipped: - break + is_flipped = False + for face in island: + if is_flipped: + break - # Using 'Sum of Edges' to detect counter clockwise https://stackoverflow.com/questions/1165647/how-to-determine-if-a-list-of-polygon-points-are-in-clockwise-order - sum = 0 - count = len(face.loops) - for i in range(count): - uv_A = face.loops[i][uv_layers].uv - uv_B = face.loops[(i+1)%count][uv_layers].uv - sum += (uv_B.x - uv_A.x) * (uv_B.y + uv_A.y) + # Using 'Sum of Edges' to detect counter clockwise https://stackoverflow.com/questions/1165647/how-to-determine-if-a-list-of-polygon-points-are-in-clockwise-order + sum = 0 + count = len(face.loops) + for i in range(count): + uv_A = face.loops[i][uv_layers].uv + uv_B = face.loops[(i+1)%count][uv_layers].uv + sum += (uv_B.x - uv_A.x) * (uv_B.y + uv_A.y) - if sum > 0: - # Flipped - is_flipped = True - break + if sum > 0: + # Flipped + is_flipped = True + break - # Select Island if flipped - if is_flipped: - for face in island: - for loop in face.loops: - loop[uv_layers].select = True + # Select Island if flipped + if is_flipped: + for face in island: + for loop in face.loops: + loop[uv_layers].select = True class Island_bounds: - faces = [] - center = Vector([0,0]) - min = Vector([0,0]) - max = Vector([0,0]) - - def __init__(self, faces): - bm = bmesh.from_edit_mesh(bpy.context.active_object.data); - uv_layers = bm.loops.layers.uv.verify(); - - # Collect topology stats - self.faces = faces - - #Select Island - bpy.ops.uv.select_all(action='DESELECT') - utilities_uv.set_selected_faces(faces) - - bounds = utilities_uv.getSelectionBBox() - self.center = bounds['center'] - self.min = bounds['min'] - self.max = bounds['max'] - - - - def isEqual(A, B): - - # Bounding Box AABB intersection? - min_x = max(A.min.x, B.min.x) - min_y = max(A.min.y, B.min.y) - max_x = min(A.max.x, B.max.x) - max_y = min(A.max.y, B.max.y) - if not (max_x < min_x or max_y < min_y): - return True - - - return False + faces = [] + center = Vector([0,0]) + min = Vector([0,0]) + max = Vector([0,0]) + + def __init__(self, faces): + bm = bmesh.from_edit_mesh(bpy.context.active_object.data); + uv_layers = bm.loops.layers.uv.verify(); + + # Collect topology stats + self.faces = faces + + #Select Island + bpy.ops.uv.select_all(action='DESELECT') + utilities_uv.set_selected_faces(faces) + + bounds = utilities_uv.getSelectionBBox() + self.center = bounds['center'] + self.min = bounds['min'] + self.max = bounds['max'] + + + + def isEqual(A, B): + + # Bounding Box AABB intersection? + min_x = max(A.min.x, B.min.x) + min_y = max(A.min.y, B.min.y) + max_x = min(A.max.x, B.max.x) + max_y = min(A.max.y, B.max.y) + if not (max_x < min_x or max_y < min_y): + return True + + + return False bpy.utils.register_class(op) \ No newline at end of file diff --git a/op_select_islands_identical.py b/op_select_islands_identical.py index 5912f2c..56eaf21 100644 --- a/op_select_islands_identical.py +++ b/op_select_islands_identical.py @@ -9,122 +9,122 @@ from . import utilities_uv class op(bpy.types.Operator): - bl_idname = "uv.textools_select_islands_identical" - bl_label = "Select identical" - bl_description = "Select identical UV islands with similar topology" - bl_options = {'REGISTER', 'UNDO'} + bl_idname = "uv.textools_select_islands_identical" + bl_label = "Select identical" + bl_description = "Select identical UV islands with similar topology" + bl_options = {'REGISTER', 'UNDO'} - @classmethod - def poll(cls, context): - if not bpy.context.active_object: - return False - - if bpy.context.active_object.type != 'MESH': - return False + @classmethod + def poll(cls, context): + if not bpy.context.active_object: + return False + + if bpy.context.active_object.type != 'MESH': + return False - #Only in Edit mode - if bpy.context.active_object.mode != 'EDIT': - return False + #Only in Edit mode + if bpy.context.active_object.mode != 'EDIT': + return False - #Only in UV editor mode - if bpy.context.area.type != 'IMAGE_EDITOR': - return False + #Only in UV editor mode + if bpy.context.area.type != 'IMAGE_EDITOR': + return False - ##Requires UV map - if not bpy.context.object.data.uv_layers: - return False + ##Requires UV map + if not bpy.context.object.data.uv_layers: + return False - #Not in Synced mode - if bpy.context.scene.tool_settings.use_uv_select_sync: - return False + #Not in Synced mode + if bpy.context.scene.tool_settings.use_uv_select_sync: + return False - return True + return True - def execute(self, context): - swap(self, context) - return {'FINISHED'} + def execute(self, context): + swap(self, context) + return {'FINISHED'} def swap(self, context): - bm = bmesh.from_edit_mesh(bpy.context.active_object.data) - uv_layers = bm.loops.layers.uv.verify() + bm = bmesh.from_edit_mesh(bpy.context.active_object.data) + uv_layers = bm.loops.layers.uv.verify() - # Get selected island - islands = utilities_uv.getSelectionIslands() + # Get selected island + islands = utilities_uv.getSelectionIslands() - if len(islands) != 1: - self.report({'ERROR_INVALID_INPUT'}, "Please select only 1 UV Island") - return - - island_stats_source = Island_stats(islands[0]) + if len(islands) != 1: + self.report({'ERROR_INVALID_INPUT'}, "Please select only 1 UV Island") + return + + island_stats_source = Island_stats(islands[0]) - bpy.context.scene.tool_settings.uv_select_mode = 'FACE' - bpy.ops.uv.select_all(action='SELECT') + bpy.context.scene.tool_settings.uv_select_mode = 'FACE' + bpy.ops.uv.select_all(action='SELECT') - islands_all = utilities_uv.getSelectionIslands() - islands_equal = [] - for island in islands_all: - island_stats = Island_stats(island) + islands_all = utilities_uv.getSelectionIslands() + islands_equal = [] + for island in islands_all: + island_stats = Island_stats(island) - if island_stats_source.isEqual(island_stats): - islands_equal.append(island_stats.faces) + if island_stats_source.isEqual(island_stats): + islands_equal.append(island_stats.faces) - print("Islands: "+str(len(islands_equal))+"x") + print("Islands: "+str(len(islands_equal))+"x") - bpy.ops.uv.select_all(action='DESELECT') - for island in islands_equal: - for face in island: - for loop in face.loops: - if not loop[uv_layers].select: - loop[uv_layers].select = True + bpy.ops.uv.select_all(action='DESELECT') + for island in islands_equal: + for face in island: + for loop in face.loops: + if not loop[uv_layers].select: + loop[uv_layers].select = True class Island_stats: - countFaces = 0 - countVerts = 0 - faces = [] - area = 0 - countLinkedEdges = 0 - countLinkedFaces = 0 - - - def __init__(self, faces): - bm = bmesh.from_edit_mesh(bpy.context.active_object.data); - uv_layers = bm.loops.layers.uv.verify(); - - # Collect topology stats - self.faces = faces - verts = [] - for face in faces: - self.countFaces+=1 - self.area+=face.calc_area() - - for loop in face.loops: - if loop.vert not in verts: - verts.append(loop.vert) - self.countVerts+=1 - self.countLinkedEdges+= len(loop.vert.link_edges) - self.countLinkedFaces+= len(loop.vert.link_faces) - - def isEqual(self, other): - if self.countVerts != other.countVerts: - return False - if self.countFaces != other.countFaces: - return False - - if self.countLinkedEdges != other.countLinkedEdges: - return False - if self.countLinkedFaces != other.countLinkedFaces: - return False - - # area needs to be 90%+ identical - if min(self.area, other.area)/max(self.area, other.area) < 0.7: - return False - - return True + countFaces = 0 + countVerts = 0 + faces = [] + area = 0 + countLinkedEdges = 0 + countLinkedFaces = 0 + + + def __init__(self, faces): + bm = bmesh.from_edit_mesh(bpy.context.active_object.data); + uv_layers = bm.loops.layers.uv.verify(); + + # Collect topology stats + self.faces = faces + verts = [] + for face in faces: + self.countFaces+=1 + self.area+=face.calc_area() + + for loop in face.loops: + if loop.vert not in verts: + verts.append(loop.vert) + self.countVerts+=1 + self.countLinkedEdges+= len(loop.vert.link_edges) + self.countLinkedFaces+= len(loop.vert.link_faces) + + def isEqual(self, other): + if self.countVerts != other.countVerts: + return False + if self.countFaces != other.countFaces: + return False + + if self.countLinkedEdges != other.countLinkedEdges: + return False + if self.countLinkedFaces != other.countLinkedFaces: + return False + + # area needs to be 90%+ identical + if min(self.area, other.area)/max(self.area, other.area) < 0.7: + return False + + return True bpy.utils.register_class(op) \ No newline at end of file diff --git a/op_select_islands_outline.py b/op_select_islands_outline.py index 750445c..1a440d9 100644 --- a/op_select_islands_outline.py +++ b/op_select_islands_outline.py @@ -9,70 +9,70 @@ from . import utilities_uv from . import utilities_ui class op(bpy.types.Operator): - bl_idname = "uv.textools_select_islands_outline" - bl_label = "Select Overlap" - bl_description = "Select island edge bounds" - bl_options = {'REGISTER', 'UNDO'} + bl_idname = "uv.textools_select_islands_outline" + bl_label = "Select Overlap" + bl_description = "Select island edge bounds" + bl_options = {'REGISTER', 'UNDO'} - @classmethod - def poll(cls, context): - if not bpy.context.active_object: - return False - - if bpy.context.active_object.type != 'MESH': - return False + @classmethod + def poll(cls, context): + if not bpy.context.active_object: + return False + + if bpy.context.active_object.type != 'MESH': + return False - #Requires UV map - if not bpy.context.object.data.uv_layers: - return False + #Requires UV map + if not bpy.context.object.data.uv_layers: + return False - return True + return True - def execute(self, context): - select_outline(context) - return {'FINISHED'} + def execute(self, context): + select_outline(context) + return {'FINISHED'} def select_outline(context): - #Only in Edit mode - if bpy.context.active_object.mode != 'EDIT': - bpy.ops.object.mode_set(mode='EDIT') + #Only in Edit mode + if bpy.context.active_object.mode != 'EDIT': + bpy.ops.object.mode_set(mode='EDIT') - bpy.context.scene.tool_settings.use_uv_select_sync = False + bpy.context.scene.tool_settings.use_uv_select_sync = False - bm = bmesh.from_edit_mesh(bpy.context.active_object.data); - uv_layers = bm.loops.layers.uv.verify(); + bm = bmesh.from_edit_mesh(bpy.context.active_object.data); + uv_layers = bm.loops.layers.uv.verify(); - bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE') - bpy.ops.mesh.select_all(action='DESELECT') + bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE') + bpy.ops.mesh.select_all(action='DESELECT') - # Store previous edge seams - edges_seam = [edge for edge in bm.edges if edge.seam] - + # Store previous edge seams + edges_seam = [edge for edge in bm.edges if edge.seam] + - contextViewUV = utilities_ui.GetContextViewUV() - if not contextViewUV: - self.report({'ERROR_INVALID_INPUT'}, "This tool requires an available UV/Image view.") - return + contextViewUV = utilities_ui.GetContextViewUV() + if not contextViewUV: + self.report({'ERROR_INVALID_INPUT'}, "This tool requires an available UV/Image view.") + return - # Create seams from islands - bpy.ops.uv.seams_from_islands(contextViewUV) - edges_islands = [edge for edge in bm.edges if edge.seam] + # Create seams from islands + bpy.ops.uv.seams_from_islands(contextViewUV) + edges_islands = [edge for edge in bm.edges if edge.seam] - # Clear seams - for edge in edges_islands: - edge.seam = False + # Clear seams + for edge in edges_islands: + edge.seam = False - # Select island edges - bpy.ops.mesh.select_all(action='DESELECT') - for edge in edges_islands: - edge.select = True + # Select island edges + bpy.ops.mesh.select_all(action='DESELECT') + for edge in edges_islands: + edge.select = True - # Restore seam selection - for edge in edges_seam: - edge.seam = True + # Restore seam selection + for edge in edges_seam: + edge.seam = True bpy.utils.register_class(op) \ No newline at end of file diff --git a/op_select_islands_overlap.py b/op_select_islands_overlap.py index a5c4263..1026029 100644 --- a/op_select_islands_overlap.py +++ b/op_select_islands_overlap.py @@ -10,131 +10,131 @@ import imp imp.reload(utilities_uv) class op(bpy.types.Operator): - bl_idname = "uv.textools_select_islands_overlap" - bl_label = "Select outline" - bl_description = "Select all overlapping UV islands" - bl_options = {'REGISTER', 'UNDO'} + bl_idname = "uv.textools_select_islands_overlap" + bl_label = "Select outline" + bl_description = "Select all overlapping UV islands" + bl_options = {'REGISTER', 'UNDO'} - @classmethod - def poll(cls, context): - if not bpy.context.active_object: - return False + @classmethod + def poll(cls, context): + if not bpy.context.active_object: + return False - if bpy.context.active_object.type != 'MESH': - return False + if bpy.context.active_object.type != 'MESH': + return False - #Only in Edit mode - if bpy.context.active_object.mode != 'EDIT': - return False + #Only in Edit mode + if bpy.context.active_object.mode != 'EDIT': + return False - #Only in UV editor mode - if bpy.context.area.type != 'IMAGE_EDITOR': - return False + #Only in UV editor mode + if bpy.context.area.type != 'IMAGE_EDITOR': + return False - ##Requires UV map - if not bpy.context.object.data.uv_layers: - return False + ##Requires UV map + if not bpy.context.object.data.uv_layers: + return False - #Not in Synced mode - if bpy.context.scene.tool_settings.use_uv_select_sync: - return False - - return True + #Not in Synced mode + if bpy.context.scene.tool_settings.use_uv_select_sync: + return False + + return True - def execute(self, context): - - selectOverlap(context) - return {'FINISHED'} + def execute(self, context): + + selectOverlap(context) + return {'FINISHED'} def selectOverlap(context): - print("Execute op_select_islands_overlap") + print("Execute op_select_islands_overlap") - # https://developer.blender.org/D2865 + # https://developer.blender.org/D2865 - bm = bmesh.from_edit_mesh(bpy.context.active_object.data) - uv_layers = bm.loops.layers.uv.verify() + bm = bmesh.from_edit_mesh(bpy.context.active_object.data) + uv_layers = bm.loops.layers.uv.verify() - bpy.context.scene.tool_settings.uv_select_mode = 'FACE' - bpy.ops.uv.select_all(action='SELECT') + bpy.context.scene.tool_settings.uv_select_mode = 'FACE' + bpy.ops.uv.select_all(action='SELECT') - islands_all = utilities_uv.getSelectionIslands() - # count = len(islands_all) + islands_all = utilities_uv.getSelectionIslands() + # count = len(islands_all) - islands_bounds = [] - for island in islands_all: - islands_bounds.append( Island_bounds( island ) ) - + islands_bounds = [] + for island in islands_all: + islands_bounds.append( Island_bounds( island ) ) + - groups = [] - unmatched = islands_bounds.copy() + groups = [] + unmatched = islands_bounds.copy() - for islandA in islands_bounds: - if islandA in unmatched: + for islandA in islands_bounds: + if islandA in unmatched: - group = [islandA] - for islandB in unmatched: - if islandA != islandB and islandA.isEqual(islandB): - group.append(islandB) + group = [islandA] + for islandB in unmatched: + if islandA != islandB and islandA.isEqual(islandB): + group.append(islandB) - for item in group: - unmatched.remove(item) + for item in group: + unmatched.remove(item) - groups.append(group) + groups.append(group) - print("Group: {} islands, unmatched: {}x".format(len(group), len(unmatched))) - # groups.append( ) + print("Group: {} islands, unmatched: {}x".format(len(group), len(unmatched))) + # groups.append( ) - bpy.ops.uv.select_all(action='DESELECT') - for group in groups: - if len(group) > 1: - for i in range(1, len(group)): - utilities_uv.set_selected_faces( group[i].faces ) + bpy.ops.uv.select_all(action='DESELECT') + for group in groups: + if len(group) > 1: + for i in range(1, len(group)): + utilities_uv.set_selected_faces( group[i].faces ) - print("Groups: "+str(len(groups))) + print("Groups: "+str(len(groups))) class Island_bounds: - faces = [] - center = Vector([0,0]) - min = Vector([0,0]) - max = Vector([0,0]) - - def __init__(self, faces): - bm = bmesh.from_edit_mesh(bpy.context.active_object.data); - uv_layers = bm.loops.layers.uv.verify(); - - # Collect topology stats - self.faces = faces - - #Select Island - bpy.ops.uv.select_all(action='DESELECT') - utilities_uv.set_selected_faces(faces) - - bounds = utilities_uv.getSelectionBBox() - self.center = bounds['center'] - self.min = bounds['min'] - self.max = bounds['max'] - - - - def isEqual(A, B): - - # Bounding Box AABB intersection? - min_x = max(A.min.x, B.min.x) - min_y = max(A.min.y, B.min.y) - max_x = min(A.max.x, B.max.x) - max_y = min(A.max.y, B.max.y) - if not (max_x < min_x or max_y < min_y): - return True - - - return False + faces = [] + center = Vector([0,0]) + min = Vector([0,0]) + max = Vector([0,0]) + + def __init__(self, faces): + bm = bmesh.from_edit_mesh(bpy.context.active_object.data); + uv_layers = bm.loops.layers.uv.verify(); + + # Collect topology stats + self.faces = faces + + #Select Island + bpy.ops.uv.select_all(action='DESELECT') + utilities_uv.set_selected_faces(faces) + + bounds = utilities_uv.getSelectionBBox() + self.center = bounds['center'] + self.min = bounds['min'] + self.max = bounds['max'] + + + + def isEqual(A, B): + + # Bounding Box AABB intersection? + min_x = max(A.min.x, B.min.x) + min_y = max(A.min.y, B.min.y) + max_x = min(A.max.x, B.max.x) + max_y = min(A.max.y, B.max.y) + if not (max_x < min_x or max_y < min_y): + return True + + + return False bpy.utils.register_class(op) \ No newline at end of file diff --git a/op_smoothing_uv_islands.py b/op_smoothing_uv_islands.py index 24ed1af..411fbb0 100644 --- a/op_smoothing_uv_islands.py +++ b/op_smoothing_uv_islands.py @@ -10,58 +10,58 @@ from . import utilities_uv from . import utilities_ui class op(bpy.types.Operator): - bl_idname = "uv.textools_smoothing_uv_islands" - bl_label = "Apply smooth normals and hard edges for UV Island borders." - bl_description = "Set mesh smoothing by uv islands" - bl_options = {'REGISTER', 'UNDO'} - - @classmethod - def poll(cls, context): - if not bpy.context.active_object: - return False - - if bpy.context.active_object.type != 'MESH': - return False + bl_idname = "uv.textools_smoothing_uv_islands" + bl_label = "Apply smooth normals and hard edges for UV Island borders." + bl_description = "Set mesh smoothing by uv islands" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(cls, context): + if not bpy.context.active_object: + return False + + if bpy.context.active_object.type != 'MESH': + return False - #Requires UV map - if not bpy.context.object.data.uv_layers: - return False + #Requires UV map + if not bpy.context.object.data.uv_layers: + return False - return True - - def execute(self, context): - smooth_uv_islands(self, context) - return {'FINISHED'} + return True + + def execute(self, context): + smooth_uv_islands(self, context) + return {'FINISHED'} def smooth_uv_islands(self, context): - if bpy.context.active_object.mode != 'EDIT': - bpy.ops.object.mode_set(mode='EDIT') + if bpy.context.active_object.mode != 'EDIT': + bpy.ops.object.mode_set(mode='EDIT') - bm = bmesh.from_edit_mesh(bpy.context.active_object.data); - uv_layers = bm.loops.layers.uv.verify(); + bm = bmesh.from_edit_mesh(bpy.context.active_object.data); + uv_layers = bm.loops.layers.uv.verify(); - # Smooth everything - bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE') - bpy.ops.mesh.select_all(action='SELECT') - bpy.ops.mesh.faces_shade_smooth() - bpy.ops.mesh.mark_sharp(clear=True) + # Smooth everything + bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE') + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.faces_shade_smooth() + bpy.ops.mesh.mark_sharp(clear=True) - # Select Edges - bpy.ops.mesh.select_all(action='DESELECT') - bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE') - bpy.ops.uv.textools_select_islands_outline() - bpy.ops.mesh.mark_sharp() - bpy.ops.mesh.select_all(action='DESELECT') - - # Apply Edge split modifier - bpy.context.object.data.use_auto_smooth = True - bpy.context.object.data.auto_smooth_angle = math.pi + # Select Edges + bpy.ops.mesh.select_all(action='DESELECT') + bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE') + bpy.ops.uv.textools_select_islands_outline() + bpy.ops.mesh.mark_sharp() + bpy.ops.mesh.select_all(action='DESELECT') + + # Apply Edge split modifier + bpy.context.object.data.use_auto_smooth = True + bpy.context.object.data.auto_smooth_angle = math.pi - # bpy.ops.object.modifier_add(type='EDGE_SPLIT') - # bpy.context.object.modifiers["EdgeSplit"].use_edge_angle = False + # bpy.ops.object.modifier_add(type='EDGE_SPLIT') + # bpy.context.object.modifiers["EdgeSplit"].use_edge_angle = False - bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.mode_set(mode='OBJECT') bpy.utils.register_class(op) \ No newline at end of file diff --git a/op_texel_checker_map.py b/op_texel_checker_map.py index 6b75dda..7152a72 100644 --- a/op_texel_checker_map.py +++ b/op_texel_checker_map.py @@ -14,163 +14,163 @@ texture_modes = ['UV_GRID','COLOR_GRID','GRAVITY','NONE'] class op(bpy.types.Operator): - bl_idname = "uv.textools_texel_checker_map" - bl_label = "Checker Map" - bl_description = "Add a checker map to the selected model and UV view" - bl_options = {'REGISTER', 'UNDO'} - - @classmethod - def poll(cls, context): - if len(get_valid_objects()) == 0: - return False + bl_idname = "uv.textools_texel_checker_map" + bl_label = "Checker Map" + bl_description = "Add a checker map to the selected model and UV view" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(cls, context): + if len(get_valid_objects()) == 0: + return False - return True + return True - def execute(self, context): - assign_checker_map( - bpy.context.scene.texToolsSettings.size[0], - bpy.context.scene.texToolsSettings.size[1] - ) - return {'FINISHED'} + def execute(self, context): + assign_checker_map( + bpy.context.scene.texToolsSettings.size[0], + bpy.context.scene.texToolsSettings.size[1] + ) + return {'FINISHED'} def assign_checker_map(size_x, size_y): - # Force Object mode - if bpy.context.view_layer.objects.active != None and bpy.context.object.mode != 'OBJECT': - bpy.ops.object.mode_set(mode='OBJECT') - - # Collect Objects - objects = get_valid_objects() - - if len(objects) == 0: - self.report({'ERROR_INVALID_INPUT'}, "No UV mapped objects selected" ) - - #Change View mode to TEXTURED - for area in bpy.context.screen.areas: - if area.type == 'VIEW_3D': - for space in area.spaces: - if space.type == 'VIEW_3D': - space.shading.type = 'MATERIAL' - - - if len(objects) > 0: - - # Detect current Checker modes - mode_count = {} - for mode in texture_modes: - mode_count[mode] = 0 - - # Image sizes - image_sizes_x = [] - image_sizes_y = [] - - # Collect current modes in selected objects - for obj in objects: - image = utilities_texel.get_object_texture_image(obj) - mode = 'NONE' - if image: - if "GRAVITY" in image.name.upper(): - mode = 'GRAVITY' - - elif image.generated_type in texture_modes: - # Generated checker maps - mode = image.generated_type - - # Track image sizes - if image.size[0] not in image_sizes_x: - image_sizes_x.append(image.size[0]) - if image.size[1] not in image_sizes_y: - image_sizes_y.append(image.size[1]) - - mode_count[mode]+=1 - - - # Sort by count (returns tuple list of key,value) - mode_max_count = sorted(mode_count.items(), key=operator.itemgetter(1)) - mode_max_count.reverse() - - for key,val in mode_max_count: - print("{} = {}".format(key, val)) - - - # Determine next mode - mode = 'NONE' - if mode_max_count[0][1] == 0: - # There are no maps - mode = texture_modes[0] - - elif mode_max_count[0][0] in texture_modes: - if mode_max_count[-1][1] > 0: - # There is more than 0 of another mode, complete existing mode first - mode = mode_max_count[0][0] - - else: - # Switch to next checker mode - index = texture_modes.index(mode_max_count[0][0]) - - if texture_modes[ index ] != 'NONE' and len(image_sizes_x) > 1 or len(image_sizes_y) > 1: - # There are multiple resolutions on selected objects - mode = texture_modes[ index ] - elif texture_modes[ index ] != 'NONE' and (len(image_sizes_x) > 0 and image_sizes_x[0] != size_x) and (len(image_sizes_y) > 0 and image_sizes_y[0] != size_y): - # The selected objects have a different resolution - mode = texture_modes[ index ] - else: - # Next mode - mode = texture_modes[ (index+1)%len(texture_modes) ] - - - print("Mode: "+mode) - - if mode == 'NONE': - for obj in objects: - remove_material(obj) - - elif mode == 'GRAVITY': - image = load_image("checker_map_gravity") - for obj in objects: - apply_image(obj, image) - - else: - name = utilities_texel.get_checker_name(mode, size_x, size_y) - image = get_image(name, mode, size_x, size_y) - for obj in objects: - apply_image(obj, image) - - # Restore object selection - bpy.ops.object.mode_set(mode='OBJECT') - bpy.ops.object.select_all(action='DESELECT') - for obj in objects: - obj.select_set( state = True, view_layer = None) + # Force Object mode + if bpy.context.view_layer.objects.active != None and bpy.context.object.mode != 'OBJECT': + bpy.ops.object.mode_set(mode='OBJECT') + + # Collect Objects + objects = get_valid_objects() + + if len(objects) == 0: + self.report({'ERROR_INVALID_INPUT'}, "No UV mapped objects selected" ) + + #Change View mode to TEXTURED + for area in bpy.context.screen.areas: + if area.type == 'VIEW_3D': + for space in area.spaces: + if space.type == 'VIEW_3D': + space.shading.type = 'MATERIAL' + + + if len(objects) > 0: + + # Detect current Checker modes + mode_count = {} + for mode in texture_modes: + mode_count[mode] = 0 + + # Image sizes + image_sizes_x = [] + image_sizes_y = [] + + # Collect current modes in selected objects + for obj in objects: + image = utilities_texel.get_object_texture_image(obj) + mode = 'NONE' + if image: + if "GRAVITY" in image.name.upper(): + mode = 'GRAVITY' + + elif image.generated_type in texture_modes: + # Generated checker maps + mode = image.generated_type + + # Track image sizes + if image.size[0] not in image_sizes_x: + image_sizes_x.append(image.size[0]) + if image.size[1] not in image_sizes_y: + image_sizes_y.append(image.size[1]) + + mode_count[mode]+=1 + + + # Sort by count (returns tuple list of key,value) + mode_max_count = sorted(mode_count.items(), key=operator.itemgetter(1)) + mode_max_count.reverse() + + for key,val in mode_max_count: + print("{} = {}".format(key, val)) + + + # Determine next mode + mode = 'NONE' + if mode_max_count[0][1] == 0: + # There are no maps + mode = texture_modes[0] + + elif mode_max_count[0][0] in texture_modes: + if mode_max_count[-1][1] > 0: + # There is more than 0 of another mode, complete existing mode first + mode = mode_max_count[0][0] + + else: + # Switch to next checker mode + index = texture_modes.index(mode_max_count[0][0]) + + if texture_modes[ index ] != 'NONE' and len(image_sizes_x) > 1 or len(image_sizes_y) > 1: + # There are multiple resolutions on selected objects + mode = texture_modes[ index ] + elif texture_modes[ index ] != 'NONE' and (len(image_sizes_x) > 0 and image_sizes_x[0] != size_x) and (len(image_sizes_y) > 0 and image_sizes_y[0] != size_y): + # The selected objects have a different resolution + mode = texture_modes[ index ] + else: + # Next mode + mode = texture_modes[ (index+1)%len(texture_modes) ] + + + print("Mode: "+mode) + + if mode == 'NONE': + for obj in objects: + remove_material(obj) + + elif mode == 'GRAVITY': + image = load_image("checker_map_gravity") + for obj in objects: + apply_image(obj, image) + + else: + name = utilities_texel.get_checker_name(mode, size_x, size_y) + image = get_image(name, mode, size_x, size_y) + for obj in objects: + apply_image(obj, image) + + # Restore object selection + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.select_all(action='DESELECT') + for obj in objects: + obj.select_set( state = True, view_layer = None) - # Clean up images and materials - utilities_texel.checker_images_cleanup() + # Clean up images and materials + utilities_texel.checker_images_cleanup() - # Force redraw of viewport to update texture - # bpy.context.scene.update() - bpy.context.view_layer.update() + # Force redraw of viewport to update texture + # bpy.context.scene.update() + bpy.context.view_layer.update() def load_image(name): - pathTexture = icons_dir = os.path.join(os.path.dirname(__file__), "resources/{}.png".format(name)) - image = bpy.ops.image.open(filepath=pathTexture, relative_path=False) - if "{}.png".format(name) in bpy.data.images: - bpy.data.images["{}.png".format(name)].name = name #remove extension in name - return bpy.data.images[name]; + pathTexture = icons_dir = os.path.join(os.path.dirname(__file__), "resources/{}.png".format(name)) + image = bpy.ops.image.open(filepath=pathTexture, relative_path=False) + if "{}.png".format(name) in bpy.data.images: + bpy.data.images["{}.png".format(name)].name = name #remove extension in name + return bpy.data.images[name]; def get_valid_objects(): - # Collect Objects - objects = [] - for obj in bpy.context.selected_objects: - if obj.type == 'MESH' and obj.data.uv_layers: - objects.append(obj) + # Collect Objects + objects = [] + for obj in bpy.context.selected_objects: + if obj.type == 'MESH' and obj.data.uv_layers: + objects.append(obj) - return objects + return objects @@ -179,72 +179,72 @@ def get_valid_objects(): def remove_material(obj): - bpy.ops.object.mode_set(mode='OBJECT') - bpy.ops.object.select_all(action='DESELECT') - obj.select_set( state = True, view_layer = None) - bpy.context.view_layer.objects.active = obj - count = len(obj.material_slots) - for i in range(count): - bpy.ops.object.material_slot_remove() + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.select_all(action='DESELECT') + obj.select_set( state = True, view_layer = None) + bpy.context.view_layer.objects.active = obj + count = len(obj.material_slots) + for i in range(count): + bpy.ops.object.material_slot_remove() def apply_image(obj, image): - bpy.ops.object.mode_set(mode='OBJECT') - bpy.ops.object.select_all(action='DESELECT') - obj.select_set( state = True, view_layer = None) - bpy.context.view_layer.objects.active = obj - - # Assign Cycles material with image - - # Get Material - material = None - if image.name in bpy.data.materials: - material = bpy.data.materials[image.name] - else: - material = bpy.data.materials.new(image.name) - material.use_nodes = True - - # Assign material - if len(obj.data.materials) > 0: - obj.data.materials[0] = material - else: - obj.data.materials.append(material) - - # Setup Node - tree = material.node_tree - node = None - if "checker" in tree.nodes: - node = tree.nodes["checker"] - else: - node = tree.nodes.new("ShaderNodeTexImage") - node.name = "checker" - node.select = True - tree.nodes.active = node - node.image = image - - # LINKANDO: - tree = obj.data.materials[0].node_tree - links = tree.links - nodo1 = tree.nodes['checker'] - nodo2 = tree.nodes['Principled BSDF'] - links.new(nodo1.outputs['Color'], nodo2.inputs['Base Color']) + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.select_all(action='DESELECT') + obj.select_set( state = True, view_layer = None) + bpy.context.view_layer.objects.active = obj + + # Assign Cycles material with image + + # Get Material + material = None + if image.name in bpy.data.materials: + material = bpy.data.materials[image.name] + else: + material = bpy.data.materials.new(image.name) + material.use_nodes = True + + # Assign material + if len(obj.data.materials) > 0: + obj.data.materials[0] = material + else: + obj.data.materials.append(material) + + # Setup Node + tree = material.node_tree + node = None + if "checker" in tree.nodes: + node = tree.nodes["checker"] + else: + node = tree.nodes.new("ShaderNodeTexImage") + node.name = "checker" + node.select = True + tree.nodes.active = node + node.image = image + + # LINKANDO: + tree = obj.data.materials[0].node_tree + links = tree.links + nodo1 = tree.nodes['checker'] + nodo2 = tree.nodes['Principled BSDF'] + links.new(nodo1.outputs['Color'], nodo2.inputs['Base Color']) def get_image(name, mode, size_x, size_y): - # Image already exists? - if name in bpy.data.images: - # Update texture UV checker mode - bpy.data.images[name].generated_type = mode - return bpy.data.images[name]; - - # Create new image instead - image = bpy.data.images.new(name, width=size_x, height=size_y) - image.generated_type = mode #UV_GRID or COLOR_GRID - image.generated_width = int(size_x) - image.generated_height = int(size_y) - - return image + # Image already exists? + if name in bpy.data.images: + # Update texture UV checker mode + bpy.data.images[name].generated_type = mode + return bpy.data.images[name]; + + # Create new image instead + image = bpy.data.images.new(name, width=size_x, height=size_y) + image.generated_type = mode #UV_GRID or COLOR_GRID + image.generated_width = int(size_x) + image.generated_height = int(size_y) + + return image bpy.utils.register_class(op) diff --git a/op_texel_density_get.py b/op_texel_density_get.py index 4504638..7d9f3da 100644 --- a/op_texel_density_get.py +++ b/op_texel_density_get.py @@ -7,127 +7,127 @@ from . import utilities_texel class op(bpy.types.Operator): - bl_idname = "uv.textools_texel_density_get" - bl_label = "Get Texel size" - bl_description = "Get Pixel per unit ratio or Texel density" - bl_options = {'REGISTER', 'UNDO'} - - @classmethod - def poll(cls, context): - #Only in UV editor mode - if bpy.context.area.type != 'IMAGE_EDITOR': - return False + bl_idname = "uv.textools_texel_density_get" + bl_label = "Get Texel size" + bl_description = "Get Pixel per unit ratio or Texel density" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(cls, context): + #Only in UV editor mode + if bpy.context.area.type != 'IMAGE_EDITOR': + return False - if not bpy.context.active_object: - return False - - if len(bpy.context.selected_objects) == 0: - return False + if not bpy.context.active_object: + return False + + if len(bpy.context.selected_objects) == 0: + return False - if bpy.context.active_object.type != 'MESH': - return False + if bpy.context.active_object.type != 'MESH': + return False - if not bpy.context.object.data.uv_layers: - return False + if not bpy.context.object.data.uv_layers: + return False - # if bpy.context.object.mode == 'EDIT': - # # In edit mode requires face select mode - # if bpy.context.scene.tool_settings.mesh_select_mode[2] == False: - # return False + # if bpy.context.object.mode == 'EDIT': + # # In edit mode requires face select mode + # if bpy.context.scene.tool_settings.mesh_select_mode[2] == False: + # return False - return True + return True - def execute(self, context): - get_texel_density( - self, - context - ) - return {'FINISHED'} + def execute(self, context): + get_texel_density( + self, + context + ) + return {'FINISHED'} def get_texel_density(self, context): - print("Get texel density") - - edit_mode = bpy.context.object.mode == 'EDIT' - object_faces = utilities_texel.get_selected_object_faces() - - # Warning: No valid input objects - if len(object_faces) == 0: - self.report({'ERROR_INVALID_INPUT'}, "No UV maps or meshes selected" ) - return - - print("obj faces groups {}".format(len(object_faces))) - - # Collect Images / textures - object_images = {} - for obj in object_faces: - image = utilities_texel.get_object_texture_image(obj) - if image: - object_images[obj] = image - - # Warning: No valid images - if len(object_images) == 0: - self.report({'ERROR_INVALID_INPUT'}, "No Texture found. Assign Checker map or texture first." ) - return - - sum_area_vt = 0 - sum_area_uv = 0 - - # Get area for each triangle in view and UV - for obj in object_faces: - bpy.ops.object.mode_set(mode='OBJECT') - bpy.ops.object.select_all(action='DESELECT') - bpy.context.view_layer.objects.active = obj - obj.select_set( state = True, view_layer = None) - - # Find image of object - image = object_images[obj] - if image: - bpy.ops.object.mode_set(mode='EDIT') - bm = bmesh.from_edit_mesh(obj.data) - uv_layers = bm.loops.layers.uv.verify() - bm.faces.ensure_lookup_table() - - for index in object_faces[obj]: - face = bm.faces[index] - - # Triangle Verts - triangle_uv = [loop[uv_layers].uv for loop in face.loops ] - triangle_vt = [vert.co for vert in face.verts] - - #Triangle Areas - face_area_vt = utilities_texel.get_area_triangle( - triangle_vt[0], - triangle_vt[1], - triangle_vt[2] - ) - face_area_uv = utilities_texel.get_area_triangle_uv( - triangle_uv[0], - triangle_uv[1], - triangle_uv[2], - image.size[0], - image.size[1] - ) - sum_area_vt+= math.sqrt( face_area_vt ) - sum_area_uv+= math.sqrt( face_area_uv ) * min(image.size[0], image.size[1]) - - # Restore selection - bpy.ops.object.mode_set(mode='OBJECT') - bpy.ops.object.select_all(action='DESELECT') - for obj in object_faces: - obj.select_set( state = True, view_layer = None) - bpy.context.view_layer.objects.active = list(object_faces.keys())[0] - if edit_mode: - bpy.ops.object.mode_set(mode='EDIT') - - # print("Sum verts area {}".format(sum_area_vt)) - # print("Sum texture area {}".format(sum_area_uv)) - - if sum_area_uv == 0 or sum_area_vt == 0: - bpy.context.scene.texToolsSettings.texel_density = 0 - else: - bpy.context.scene.texToolsSettings.texel_density = sum_area_uv / sum_area_vt + print("Get texel density") + + edit_mode = bpy.context.object.mode == 'EDIT' + object_faces = utilities_texel.get_selected_object_faces() + + # Warning: No valid input objects + if len(object_faces) == 0: + self.report({'ERROR_INVALID_INPUT'}, "No UV maps or meshes selected" ) + return + + print("obj faces groups {}".format(len(object_faces))) + + # Collect Images / textures + object_images = {} + for obj in object_faces: + image = utilities_texel.get_object_texture_image(obj) + if image: + object_images[obj] = image + + # Warning: No valid images + if len(object_images) == 0: + self.report({'ERROR_INVALID_INPUT'}, "No Texture found. Assign Checker map or texture first." ) + return + + sum_area_vt = 0 + sum_area_uv = 0 + + # Get area for each triangle in view and UV + for obj in object_faces: + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.select_all(action='DESELECT') + bpy.context.view_layer.objects.active = obj + obj.select_set( state = True, view_layer = None) + + # Find image of object + image = object_images[obj] + if image: + bpy.ops.object.mode_set(mode='EDIT') + bm = bmesh.from_edit_mesh(obj.data) + uv_layers = bm.loops.layers.uv.verify() + bm.faces.ensure_lookup_table() + + for index in object_faces[obj]: + face = bm.faces[index] + + # Triangle Verts + triangle_uv = [loop[uv_layers].uv for loop in face.loops ] + triangle_vt = [vert.co for vert in face.verts] + + #Triangle Areas + face_area_vt = utilities_texel.get_area_triangle( + triangle_vt[0], + triangle_vt[1], + triangle_vt[2] + ) + face_area_uv = utilities_texel.get_area_triangle_uv( + triangle_uv[0], + triangle_uv[1], + triangle_uv[2], + image.size[0], + image.size[1] + ) + sum_area_vt+= math.sqrt( face_area_vt ) + sum_area_uv+= math.sqrt( face_area_uv ) * min(image.size[0], image.size[1]) + + # Restore selection + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.select_all(action='DESELECT') + for obj in object_faces: + obj.select_set( state = True, view_layer = None) + bpy.context.view_layer.objects.active = list(object_faces.keys())[0] + if edit_mode: + bpy.ops.object.mode_set(mode='EDIT') + + # print("Sum verts area {}".format(sum_area_vt)) + # print("Sum texture area {}".format(sum_area_uv)) + + if sum_area_uv == 0 or sum_area_vt == 0: + bpy.context.scene.texToolsSettings.texel_density = 0 + else: + bpy.context.scene.texToolsSettings.texel_density = sum_area_uv / sum_area_vt bpy.utils.register_class(op) - \ No newline at end of file + \ No newline at end of file diff --git a/op_texel_density_set.py b/op_texel_density_set.py index 8f54662..6c6b107 100644 --- a/op_texel_density_set.py +++ b/op_texel_density_set.py @@ -10,180 +10,180 @@ from . import utilities_texel from . import utilities_uv class op(bpy.types.Operator): - bl_idname = "uv.textools_texel_density_set" - bl_label = "Set Texel size" - bl_description = "Apply texel density by scaling the UV's to match the ratio" - bl_options = {'REGISTER', 'UNDO'} - - @classmethod - def poll(cls, context): - - if not bpy.context.active_object: - return False - - if len(bpy.context.selected_objects) == 0: - return False - - if bpy.context.active_object.type != 'MESH': - return False - - #Only in UV editor mode - if bpy.context.area.type != 'IMAGE_EDITOR': - return False - - #Requires UV map - if not bpy.context.object.data.uv_layers: - return False - - # if bpy.context.object.mode == 'EDIT': - # # In edit mode requires face select mode - # if bpy.context.scene.tool_settings.mesh_select_mode[2] == False: - # return False - - return True - - - def execute(self, context): - set_texel_density( - self, - context, - bpy.context.scene.texToolsSettings.texel_mode_scale, - bpy.context.scene.texToolsSettings.texel_density - ) - return {'FINISHED'} + bl_idname = "uv.textools_texel_density_set" + bl_label = "Set Texel size" + bl_description = "Apply texel density by scaling the UV's to match the ratio" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(cls, context): + + if not bpy.context.active_object: + return False + + if len(bpy.context.selected_objects) == 0: + return False + + if bpy.context.active_object.type != 'MESH': + return False + + #Only in UV editor mode + if bpy.context.area.type != 'IMAGE_EDITOR': + return False + + #Requires UV map + if not bpy.context.object.data.uv_layers: + return False + + # if bpy.context.object.mode == 'EDIT': + # # In edit mode requires face select mode + # if bpy.context.scene.tool_settings.mesh_select_mode[2] == False: + # return False + + return True + + + def execute(self, context): + set_texel_density( + self, + context, + bpy.context.scene.texToolsSettings.texel_mode_scale, + bpy.context.scene.texToolsSettings.texel_density + ) + return {'FINISHED'} def set_texel_density(self, context, mode, density): - print("Set texel density!") - - is_edit = bpy.context.object.mode == 'EDIT' - is_sync = bpy.context.scene.tool_settings.use_uv_select_sync - object_faces = utilities_texel.get_selected_object_faces() - - - # Warning: No valid input objects - if len(object_faces) == 0: - self.report({'ERROR_INVALID_INPUT'}, "No valid meshes or UV maps" ) - return - - # Collect Images / textures - object_images = {} - for obj in object_faces: - image = utilities_texel.get_object_texture_image(obj) - if image: - object_images[obj] = image - - # Warning: No valid images - if len(object_images) == 0: - self.report({'ERROR_INVALID_INPUT'}, "No Texture found. Assign Checker map or texture." ) - return - - - for obj in object_faces: - bpy.ops.object.mode_set(mode='OBJECT') - bpy.ops.object.select_all(action='DESELECT') - bpy.context.view_layer.objects.active = obj - obj.select_set( state = True, view_layer = None) - - # Find image of object - image = object_images[obj] - if image: - bpy.ops.object.mode_set(mode='EDIT') - bpy.context.scene.tool_settings.use_uv_select_sync = False - - # Store selection - utilities_uv.selection_store() - - bm = bmesh.from_edit_mesh(obj.data) - uv_layers = bm.loops.layers.uv.verify() - - # Collect groups of faces to scale together - group_faces = [] - if is_edit: - # Collect selected faces as islands - bm.faces.ensure_lookup_table() - bpy.ops.uv.select_all(action='SELECT') - group_faces = utilities_uv.getSelectionIslands() - - elif mode == 'ALL': - # Scale all UV's together - group_faces = [bm.faces] - - elif mode == 'ISLAND': - # Scale each UV idland centered - bpy.ops.mesh.select_all(action='SELECT') - bpy.ops.uv.select_all(action='SELECT') - group_faces = utilities_uv.getSelectionIslands() - - print("group_faces {}x".format(len(group_faces))) - - - for group in group_faces: - # Get triangle areas - sum_area_vt = 0 - sum_area_uv = 0 - for face in group: - # Triangle Verts - triangle_uv = [loop[uv_layers].uv for loop in face.loops ] - triangle_vt = [obj.matrix_world @ vert.co for vert in face.verts] - - #Triangle Areas - face_area_vt = utilities_texel.get_area_triangle( - triangle_vt[0], - triangle_vt[1], - triangle_vt[2] - ) - face_area_uv = utilities_texel.get_area_triangle_uv( - triangle_uv[0], - triangle_uv[1], - triangle_uv[2], - image.size[0], - image.size[1] - ) - - sum_area_vt+= math.sqrt( face_area_vt ) - sum_area_uv+= math.sqrt( face_area_uv ) * min(image.size[0], image.size[1]) - - # Apply scale to group - print("scale: {:.2f} {:.2f} {:.2f} ".format(density, sum_area_uv, sum_area_vt)) - scale = 0 - if density > 0 and sum_area_uv > 0 and sum_area_vt > 0: - scale = density / (sum_area_uv / sum_area_vt) - - # Set Scale Origin to Island or top left - if mode == 'ISLAND': - bpy.context.tool_settings.transform_pivot_point = 'MEDIAN_POINT' - elif mode == 'ALL': - bpy.context.tool_settings.transform_pivot_point = 'CURSOR' - bpy.ops.uv.cursor_set(location=(0, 1)) - - # Select Face loops and scale - bpy.ops.uv.select_all(action='DESELECT') - bpy.context.scene.tool_settings.uv_select_mode = 'VERTEX' - for face in group: - for loop in face.loops: - loop[uv_layers].select = True - - print("Scale: {} {}x".format(scale, len(group))) - bpy.ops.transform.resize(value=(scale, scale, 1), use_proportional_edit=False) - - # Restore selection - utilities_uv.selection_restore() - - # Restore selection - bpy.ops.object.mode_set(mode='OBJECT') - bpy.ops.object.select_all(action='DESELECT') - for obj in object_faces: - obj.select_set( state = True, view_layer = None) - bpy.context.view_layer.objects.active = list(object_faces.keys())[0] - - # Restore edit mode - if is_edit: - bpy.ops.object.mode_set(mode='EDIT') - - # Restore sync mode - if is_sync: - bpy.context.scene.tool_settings.use_uv_select_sync = True + print("Set texel density!") + + is_edit = bpy.context.object.mode == 'EDIT' + is_sync = bpy.context.scene.tool_settings.use_uv_select_sync + object_faces = utilities_texel.get_selected_object_faces() + + + # Warning: No valid input objects + if len(object_faces) == 0: + self.report({'ERROR_INVALID_INPUT'}, "No valid meshes or UV maps" ) + return + + # Collect Images / textures + object_images = {} + for obj in object_faces: + image = utilities_texel.get_object_texture_image(obj) + if image: + object_images[obj] = image + + # Warning: No valid images + if len(object_images) == 0: + self.report({'ERROR_INVALID_INPUT'}, "No Texture found. Assign Checker map or texture." ) + return + + + for obj in object_faces: + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.select_all(action='DESELECT') + bpy.context.view_layer.objects.active = obj + obj.select_set( state = True, view_layer = None) + + # Find image of object + image = object_images[obj] + if image: + bpy.ops.object.mode_set(mode='EDIT') + bpy.context.scene.tool_settings.use_uv_select_sync = False + + # Store selection + utilities_uv.selection_store() + + bm = bmesh.from_edit_mesh(obj.data) + uv_layers = bm.loops.layers.uv.verify() + + # Collect groups of faces to scale together + group_faces = [] + if is_edit: + # Collect selected faces as islands + bm.faces.ensure_lookup_table() + bpy.ops.uv.select_all(action='SELECT') + group_faces = utilities_uv.getSelectionIslands() + + elif mode == 'ALL': + # Scale all UV's together + group_faces = [bm.faces] + + elif mode == 'ISLAND': + # Scale each UV idland centered + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.uv.select_all(action='SELECT') + group_faces = utilities_uv.getSelectionIslands() + + print("group_faces {}x".format(len(group_faces))) + + + for group in group_faces: + # Get triangle areas + sum_area_vt = 0 + sum_area_uv = 0 + for face in group: + # Triangle Verts + triangle_uv = [loop[uv_layers].uv for loop in face.loops ] + triangle_vt = [obj.matrix_world @ vert.co for vert in face.verts] + + #Triangle Areas + face_area_vt = utilities_texel.get_area_triangle( + triangle_vt[0], + triangle_vt[1], + triangle_vt[2] + ) + face_area_uv = utilities_texel.get_area_triangle_uv( + triangle_uv[0], + triangle_uv[1], + triangle_uv[2], + image.size[0], + image.size[1] + ) + + sum_area_vt+= math.sqrt( face_area_vt ) + sum_area_uv+= math.sqrt( face_area_uv ) * min(image.size[0], image.size[1]) + + # Apply scale to group + print("scale: {:.2f} {:.2f} {:.2f} ".format(density, sum_area_uv, sum_area_vt)) + scale = 0 + if density > 0 and sum_area_uv > 0 and sum_area_vt > 0: + scale = density / (sum_area_uv / sum_area_vt) + + # Set Scale Origin to Island or top left + if mode == 'ISLAND': + bpy.context.tool_settings.transform_pivot_point = 'MEDIAN_POINT' + elif mode == 'ALL': + bpy.context.tool_settings.transform_pivot_point = 'CURSOR' + bpy.ops.uv.cursor_set(location=(0, 1)) + + # Select Face loops and scale + bpy.ops.uv.select_all(action='DESELECT') + bpy.context.scene.tool_settings.uv_select_mode = 'VERTEX' + for face in group: + for loop in face.loops: + loop[uv_layers].select = True + + print("Scale: {} {}x".format(scale, len(group))) + bpy.ops.transform.resize(value=(scale, scale, 1), use_proportional_edit=False) + + # Restore selection + utilities_uv.selection_restore() + + # Restore selection + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.select_all(action='DESELECT') + for obj in object_faces: + obj.select_set( state = True, view_layer = None) + bpy.context.view_layer.objects.active = list(object_faces.keys())[0] + + # Restore edit mode + if is_edit: + bpy.ops.object.mode_set(mode='EDIT') + + # Restore sync mode + if is_sync: + bpy.context.scene.tool_settings.use_uv_select_sync = True bpy.utils.register_class(op) diff --git a/op_texture_open.py b/op_texture_open.py index 84d448b..d73c261 100644 --- a/op_texture_open.py +++ b/op_texture_open.py @@ -9,42 +9,42 @@ from . import utilities_bake class op(bpy.types.Operator): - bl_idname = "uv.textools_texture_open" - bl_label = "Open Texture" - bl_description = "Open the texture on the system" + bl_idname = "uv.textools_texture_open" + bl_label = "Open Texture" + bl_description = "Open the texture on the system" - name : bpy.props.StringProperty( - name="image name", - default = "" - ) + name : bpy.props.StringProperty( + name="image name", + default = "" + ) - @classmethod - def poll(cls, context): - return True - - def execute(self, context): - open_texture(self, context) - return {'FINISHED'} + @classmethod + def poll(cls, context): + return True + + def execute(self, context): + open_texture(self, context) + return {'FINISHED'} def open_texture(self, context): - print("Info") - if self.name in bpy.data.images: - image = bpy.data.images[self.name] - - if image.filepath != "": - path = bpy.path.abspath(image.filepath) - # https://meshlogic.github.io/posts/blender/addons/extra-image-list/ - # https://docs.blender.org/api/blender_python_api_2_78_release/bpy.ops.image.html - print("Open: {}".format(path)) - - if sys.platform == "win32": - os.startfile(path) - else: - opener ="open" if sys.platform == "darwin" else "xdg-open" - subprocess.call([opener, path]) + print("Info") + if self.name in bpy.data.images: + image = bpy.data.images[self.name] + + if image.filepath != "": + path = bpy.path.abspath(image.filepath) + # https://meshlogic.github.io/posts/blender/addons/extra-image-list/ + # https://docs.blender.org/api/blender_python_api_2_78_release/bpy.ops.image.html + print("Open: {}".format(path)) + + if sys.platform == "win32": + os.startfile(path) + else: + opener ="open" if sys.platform == "darwin" else "xdg-open" + subprocess.call([opener, path]) bpy.utils.register_class(op) \ No newline at end of file diff --git a/op_texture_preview.py b/op_texture_preview.py index 78bab23..7be3ac7 100644 --- a/op_texture_preview.py +++ b/op_texture_preview.py @@ -13,89 +13,89 @@ material_prefix = "TT_atlas_" gamma = 2.2 class op(bpy.types.Operator): - bl_idname = "uv.textools_texture_preview" - bl_label = "Preview Texture" - bl_description = "Preview the current UV image view background image on the selected object." - bl_options = {'REGISTER', 'UNDO'} - - - @classmethod - def poll(cls, context): - if not bpy.context.active_object: - return False - - if len(settings.sets) == 0: - return False - - # Only when we have a background image - for area in bpy.context.screen.areas: - if area.type == 'IMAGE_EDITOR': - return area.spaces[0].image - - return False - - def execute(self, context): - print("PREVIEW TEXTURE????") - preview_texture(self, context) - return {'FINISHED'} + bl_idname = "uv.textools_texture_preview" + bl_label = "Preview Texture" + bl_description = "Preview the current UV image view background image on the selected object." + bl_options = {'REGISTER', 'UNDO'} + + + @classmethod + def poll(cls, context): + if not bpy.context.active_object: + return False + + if len(settings.sets) == 0: + return False + + # Only when we have a background image + for area in bpy.context.screen.areas: + if area.type == 'IMAGE_EDITOR': + return area.spaces[0].image + + return False + + def execute(self, context): + print("PREVIEW TEXTURE????") + preview_texture(self, context) + return {'FINISHED'} def preview_texture(self, context): - # Collect all low objects from bake sets - objects = [obj for s in settings.sets for obj in s.objects_low if obj.data.uv_layers] - - # Get view 3D area - view_area = None - for area in bpy.context.screen.areas: - if area.type == 'VIEW_3D': - view_area = area - - # Exit existing local view - # if view_area and view_area.spaces[0].local_view: - # bpy.ops.view3d.localview({'area': view_area}) - # return - - - # Get background image - image = None - for area in bpy.context.screen.areas: - if area.type == 'IMAGE_EDITOR': - image = area.spaces[0].image - break - - if image: - for obj in objects: - print("Map {}".format(obj.name)) - - bpy.ops.object.mode_set(mode='OBJECT') - bpy.ops.object.select_all(action='DESELECT') - obj.select_set( state = True, view_layer = None) - bpy.context.view_layer.objects.active = obj - - for i in range(len(obj.material_slots)): - bpy.ops.object.material_slot_remove() - - #Create material with image - bpy.ops.object.material_slot_add() - obj.material_slots[0].material = utilities_bake.get_image_material(image) - obj.display_type = 'TEXTURED' - - - # Re-Select objects - bpy.ops.object.select_all(action='DESELECT') - for obj in objects: - obj.select_set( state = True, view_layer = None) - - if view_area: - #Change View mode to TEXTURED - for space in view_area.spaces: - if space.type == 'VIEW_3D': - space.shading.type = 'MATERIAL' - - # Enter local view - # bpy.ops.view3d.localview({'area': view_area}) - # bpy.ops.ui.textools_popup('INVOKE_DEFAULT', message="Object is in isolated view") + # Collect all low objects from bake sets + objects = [obj for s in settings.sets for obj in s.objects_low if obj.data.uv_layers] + + # Get view 3D area + view_area = None + for area in bpy.context.screen.areas: + if area.type == 'VIEW_3D': + view_area = area + + # Exit existing local view + # if view_area and view_area.spaces[0].local_view: + # bpy.ops.view3d.localview({'area': view_area}) + # return + + + # Get background image + image = None + for area in bpy.context.screen.areas: + if area.type == 'IMAGE_EDITOR': + image = area.spaces[0].image + break + + if image: + for obj in objects: + print("Map {}".format(obj.name)) + + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.select_all(action='DESELECT') + obj.select_set( state = True, view_layer = None) + bpy.context.view_layer.objects.active = obj + + for i in range(len(obj.material_slots)): + bpy.ops.object.material_slot_remove() + + #Create material with image + bpy.ops.object.material_slot_add() + obj.material_slots[0].material = utilities_bake.get_image_material(image) + obj.display_type = 'TEXTURED' + + + # Re-Select objects + bpy.ops.object.select_all(action='DESELECT') + for obj in objects: + obj.select_set( state = True, view_layer = None) + + if view_area: + #Change View mode to TEXTURED + for space in view_area.spaces: + if space.type == 'VIEW_3D': + space.shading.type = 'MATERIAL' + + # Enter local view + # bpy.ops.view3d.localview({'area': view_area}) + # bpy.ops.ui.textools_popup('INVOKE_DEFAULT', message="Object is in isolated view") bpy.utils.register_class(op) \ No newline at end of file diff --git a/op_texture_reload_all.py b/op_texture_reload_all.py index 4e342c7..8d730dc 100644 --- a/op_texture_reload_all.py +++ b/op_texture_reload_all.py @@ -6,59 +6,59 @@ from collections import defaultdict from math import pi class op(bpy.types.Operator): - bl_idname = "uv.textools_texture_reload_all" - bl_label = "Reload Textures and remove unused Textures" - bl_description = "Reload all textures" + bl_idname = "uv.textools_texture_reload_all" + bl_label = "Reload Textures and remove unused Textures" + bl_description = "Reload all textures" - @classmethod - def poll(cls, context): - return True + @classmethod + def poll(cls, context): + return True - def execute(self, context): - main(context) - return {'FINISHED'} + def execute(self, context): + main(context) + return {'FINISHED'} def main(context): - count_clear_mat = 0 - count_clear_img = 0 - count_reload = 0 + count_clear_mat = 0 + count_clear_img = 0 + count_reload = 0 - for material in bpy.data.materials: - if not material.users: - count_clear_mat+=1 - material.user_clear() - bpy.data.materials.remove(material) + for material in bpy.data.materials: + if not material.users: + count_clear_mat+=1 + material.user_clear() + bpy.data.materials.remove(material) - # Clean up unused images - for image in bpy.data.images: - if not image.users: - count_clear_img+=1 - image.user_clear() - bpy.data.images.remove(image) + # Clean up unused images + for image in bpy.data.images: + if not image.users: + count_clear_img+=1 + image.user_clear() + bpy.data.images.remove(image) - #Reload all File images - for img in bpy.data.images : - if img.source == 'FILE' : - count_reload+=1 - img.reload() + #Reload all File images + for img in bpy.data.images : + if img.source == 'FILE' : + count_reload+=1 + img.reload() - # Refresh vieport texture - for window in bpy.context.window_manager.windows: - screen = window.screen - for area in screen.areas: - area.tag_redraw() + # Refresh vieport texture + for window in bpy.context.window_manager.windows: + screen = window.screen + for area in screen.areas: + area.tag_redraw() - # Show popup on cleared & reloaded items - message = "" - if count_reload > 0: - message+="{}x reloaded. ".format(count_reload) - if count_clear_mat > 0: - message+="{}x mat cleared. ".format(count_clear_mat) - if count_clear_img > 0: - message+="{}x img cleared.".format(count_clear_img) + # Show popup on cleared & reloaded items + message = "" + if count_reload > 0: + message+="{}x reloaded. ".format(count_reload) + if count_clear_mat > 0: + message+="{}x mat cleared. ".format(count_clear_mat) + if count_clear_img > 0: + message+="{}x img cleared.".format(count_clear_img) - if len(message) > 0: - bpy.ops.ui.textools_popup('INVOKE_DEFAULT', message=message) - -bpy.utils.register_class(op) + if len(message) > 0: + bpy.ops.ui.textools_popup('INVOKE_DEFAULT', message=message) + +bpy.utils.register_class(op) diff --git a/op_texture_remove.py b/op_texture_remove.py index d4ff43f..8d103aa 100644 --- a/op_texture_remove.py +++ b/op_texture_remove.py @@ -10,34 +10,34 @@ from . import utilities_bake class op(bpy.types.Operator): - bl_idname = "uv.textools_texture_remove" - bl_label = "Remove Texture" - bl_description = "Remove the texture" - bl_options = {'REGISTER', 'UNDO'} + bl_idname = "uv.textools_texture_remove" + bl_label = "Remove Texture" + bl_description = "Remove the texture" + bl_options = {'REGISTER', 'UNDO'} - name : bpy.props.StringProperty( - name="image name", - default = "" - ) + name : bpy.props.StringProperty( + name="image name", + default = "" + ) - @classmethod - def poll(cls, context): - return True - - def execute(self, context): - remove_texture(self.name) - return {'FINISHED'} + @classmethod + def poll(cls, context): + return True + + def execute(self, context): + remove_texture(self.name) + return {'FINISHED'} def remove_texture(name): - print("Save image.. "+name) + print("Save image.. "+name) - if name in bpy.data.images: - bpy.data.images.remove( bpy.data.images[name] ) + if name in bpy.data.images: + bpy.data.images.remove( bpy.data.images[name] ) bpy.utils.register_class(op) \ No newline at end of file diff --git a/op_texture_save.py b/op_texture_save.py index 7d87628..caf8daf 100644 --- a/op_texture_save.py +++ b/op_texture_save.py @@ -10,100 +10,100 @@ from . import utilities_bake class op(bpy.types.Operator): - bl_idname = "uv.textools_texture_save" - bl_label = "Save Texture" - bl_description = "Save the texture" + bl_idname = "uv.textools_texture_save" + bl_label = "Save Texture" + bl_description = "Save the texture" - name : bpy.props.StringProperty( - name="image name", - default = "" - ) + name : bpy.props.StringProperty( + name="image name", + default = "" + ) - # Properties used by the file browser - # filepath = bpy.props.StringProperty(subtype="FILE_PATH") - # http://nullege.com/codes/show/src%40b%40l%40blenderpython-HEAD%40scripts%40addons_extern%40io_scene_valvesource%40import_smd.py/90/bpy.context.window_manager.fileselect_add/python - filepath : bpy.props.StringProperty(name="myName.png", description="Texture filepath", maxlen=1024, default="bla bla.png") - filter_folder : BoolProperty(name="Filter folders", description="", default=True, options={'HIDDEN'}) - filter_glob : StringProperty(default="*.png;*.tga;*.jpg;*.tif;*.exr", options={'HIDDEN'}) + # Properties used by the file browser + # filepath = bpy.props.StringProperty(subtype="FILE_PATH") + # http://nullege.com/codes/show/src%40b%40l%40blenderpython-HEAD%40scripts%40addons_extern%40io_scene_valvesource%40import_smd.py/90/bpy.context.window_manager.fileselect_add/python + filepath : bpy.props.StringProperty(name="myName.png", description="Texture filepath", maxlen=1024, default="bla bla.png") + filter_folder : BoolProperty(name="Filter folders", description="", default=True, options={'HIDDEN'}) + filter_glob : StringProperty(default="*.png;*.tga;*.jpg;*.tif;*.exr", options={'HIDDEN'}) - def invoke(self, context, event): - # if self.filepath == "": - # self.filepath = bpy.context.scene.FBXBundleSettings.path - # blend_filepath = context.blend_data.filepath - # https://blender.stackexchange.com/questions/6159/changing-default-text-value-in-file-dialogue - context.window_manager.fileselect_add(self) - return {'RUNNING_MODAL'} + def invoke(self, context, event): + # if self.filepath == "": + # self.filepath = bpy.context.scene.FBXBundleSettings.path + # blend_filepath = context.blend_data.filepath + # https://blender.stackexchange.com/questions/6159/changing-default-text-value-in-file-dialogue + context.window_manager.fileselect_add(self) + return {'RUNNING_MODAL'} - def draw(self, context): - layout = self.layout + def draw(self, context): + layout = self.layout - layout.label(text="Choose your Unity Asset directory") + layout.label(text="Choose your Unity Asset directory") - @classmethod - def poll(cls, context): - return True + @classmethod + def poll(cls, context): + return True - def execute(self, context): - save_texture(self.filepath) - return {'FINISHED'} + def execute(self, context): + save_texture(self.filepath) + return {'FINISHED'} def save_texture(path): - print("Save image.. "+path) + print("Save image.. "+path) # class op(bpy.types.Operator): -# bl_idname = "uv.textools_texture_save" -# bl_label = "Save Texture" -# bl_description = "Save the texture" +# bl_idname = "uv.textools_texture_save" +# bl_label = "Save Texture" +# bl_description = "Save the texture" -# name = bpy.props.StringProperty( -# name="image name", -# default = "" -# ) +# name = bpy.props.StringProperty( +# name="image name", +# default = "" +# ) -# @classmethod -# def poll(cls, context): -# return True - -# def execute(self, context): -# save_texture(self, context) -# return {'FINISHED'} +# @classmethod +# def poll(cls, context): +# return True + +# def execute(self, context): +# save_texture(self, context) +# return {'FINISHED'} - + ''' class op_ui_image_save(bpy.types.Operator): - bl_idname = "uv.textools_ui_image_save" - bl_label = "Save image" - bl_description = "Save this image" - - image_name = bpy.props.StringProperty( - name="image name", - default = "" - ) - - @classmethod - def poll(cls, context): - return True - - def execute(self, context): - # bpy.context.scene.tool_settings.use_uv_select_sync = False - # bpy.ops.mesh.select_all(action='SELECT') - - print("Saving image {}".format(self.image_name)) - # bpy.ops.image.save_as() - return {'FINISHED'} + bl_idname = "uv.textools_ui_image_save" + bl_label = "Save image" + bl_description = "Save this image" + + image_name = bpy.props.StringProperty( + name="image name", + default = "" + ) + + @classmethod + def poll(cls, context): + return True + + def execute(self, context): + # bpy.context.scene.tool_settings.use_uv_select_sync = False + # bpy.ops.mesh.select_all(action='SELECT') + + print("Saving image {}".format(self.image_name)) + # bpy.ops.image.save_as() + return {'FINISHED'} ''' bpy.utils.register_class(op) \ No newline at end of file diff --git a/op_texture_select.py b/op_texture_select.py index c020a57..ba0b2fa 100644 --- a/op_texture_select.py +++ b/op_texture_select.py @@ -8,77 +8,77 @@ from . import utilities_bake from . import op_bake class op(bpy.types.Operator): - bl_idname = "uv.textools_texture_select" - bl_label = "Select Texture" - bl_description = "Select the texture and bake mode" - bl_options = {'REGISTER', 'UNDO'} + bl_idname = "uv.textools_texture_select" + bl_label = "Select Texture" + bl_description = "Select the texture and bake mode" + bl_options = {'REGISTER', 'UNDO'} - name : bpy.props.StringProperty( - name="image name", - default = "" - ) + name : bpy.props.StringProperty( + name="image name", + default = "" + ) - @classmethod - def poll(cls, context): - return True - - def execute(self, context): - select_texture(self, context) - return {'FINISHED'} + @classmethod + def poll(cls, context): + return True + + def execute(self, context): + select_texture(self, context) + return {'FINISHED'} def select_texture(self, context): - print("Select "+self.name) - - - # Set bake mode - for mode in op_bake.modes: - if mode in self.name: - print("Found mode: "+mode) - - prop = bpy.context.scene.bl_rna.properties["TT_bake_mode"] - enum_values = [e.identifier for e in prop.enum_items] - - # find matching enum - for key in enum_values: - print("TT_bake "+key) - if mode in key: - print("set m: "+key) - bpy.context.scene.TT_bake_mode = key - break; - - break - - # Set background image - if self.name in bpy.data.images: - image = bpy.data.images[self.name] - for area in bpy.context.screen.areas: - if area.type == 'IMAGE_EDITOR': - area.spaces[0].image = image + print("Select "+self.name) + + + # Set bake mode + for mode in op_bake.modes: + if mode in self.name: + print("Found mode: "+mode) + + prop = bpy.context.scene.bl_rna.properties["TT_bake_mode"] + enum_values = [e.identifier for e in prop.enum_items] + + # find matching enum + for key in enum_values: + print("TT_bake "+key) + if mode in key: + print("set m: "+key) + bpy.context.scene.TT_bake_mode = key + break; + + break + + # Set background image + if self.name in bpy.data.images: + image = bpy.data.images[self.name] + for area in bpy.context.screen.areas: + if area.type == 'IMAGE_EDITOR': + area.spaces[0].image = image ''' class op_ui_image_select(bpy.types.Operator): - bl_idname = "uv.textools_ui_image_select" - bl_label = "Select image" - bl_description = "Select this image" - - image_name = bpy.props.StringProperty( - name="image name", - default = "" - ) - - @classmethod - def poll(cls, context): - return True - - def execute(self, context): - # bpy.context.scene.tool_settings.use_uv_select_sync = False - # bpy.ops.mesh.select_all(action='SELECT') - - print("Select image {}".format(self.image_name)) - # bpy.ops.image.save_as() - return {'FINISHED'} + bl_idname = "uv.textools_ui_image_select" + bl_label = "Select image" + bl_description = "Select this image" + + image_name = bpy.props.StringProperty( + name="image name", + default = "" + ) + + @classmethod + def poll(cls, context): + return True + + def execute(self, context): + # bpy.context.scene.tool_settings.use_uv_select_sync = False + # bpy.ops.mesh.select_all(action='SELECT') + + print("Select image {}".format(self.image_name)) + # bpy.ops.image.save_as() + return {'FINISHED'} ''' bpy.utils.register_class(op) \ No newline at end of file diff --git a/op_unwrap_edge_peel.py b/op_unwrap_edge_peel.py index 6aaf0f6..93d92d9 100644 --- a/op_unwrap_edge_peel.py +++ b/op_unwrap_edge_peel.py @@ -9,87 +9,87 @@ from . import utilities_uv from . import utilities_ui class op(bpy.types.Operator): - bl_idname = "uv.textools_unwrap_edge_peel" - bl_label = "Peel Edge" - bl_description = "Unwrap pipe along selected edges" - bl_options = {'REGISTER', 'UNDO'} - - @classmethod - def poll(cls, context): + bl_idname = "uv.textools_unwrap_edge_peel" + bl_label = "Peel Edge" + bl_description = "Unwrap pipe along selected edges" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(cls, context): - if not bpy.context.active_object: - return False + if not bpy.context.active_object: + return False - if bpy.context.active_object.type != 'MESH': - return False + if bpy.context.active_object.type != 'MESH': + return False - #Only in Edit mode - if bpy.context.active_object.mode != 'EDIT': - return False + #Only in Edit mode + if bpy.context.active_object.mode != 'EDIT': + return False - # Need view Face mode - if tuple(bpy.context.scene.tool_settings.mesh_select_mode)[1] == False: - return False + # Need view Face mode + if tuple(bpy.context.scene.tool_settings.mesh_select_mode)[1] == False: + return False - return True + return True - def execute(self, context): - unwrap_edges_pipe(self, context) - return {'FINISHED'} + def execute(self, context): + unwrap_edges_pipe(self, context) + return {'FINISHED'} def unwrap_edges_pipe(self, context): - bm = bmesh.from_edit_mesh(bpy.context.active_object.data) - uv_layers = bm.loops.layers.uv.verify() - - - contextViewUV = utilities_ui.GetContextViewUV() - if not contextViewUV: - self.report({'ERROR_INVALID_INPUT'}, "This tool requires an available UV/Image view.") - return - - # selected_initial = [edge for edge in bm.edges if edge.select] - selected_edges = [] - selected_faces = [] - - # Extend loop selection - bpy.ops.mesh.loop_multi_select(ring=False) - selected_edges = [edge for edge in bm.edges if edge.select] - - if len(selected_edges) == 0: - self.report({'ERROR_INVALID_INPUT'}, "No edges selected in the view" ) - return - - # Convert linked selection to single UV island - bpy.ops.mesh.select_linked(delimit=set()) - bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE') - bpy.ops.uv.textools_unwrap_faces_iron() - selected_faces = [face for face in bm.faces if face.select] - - if len(selected_faces) == 0: - self.report({'ERROR_INVALID_INPUT'}, "No faces available" ) - return - - # Mark previous selected edges as Seam - bpy.ops.mesh.select_all(action='DESELECT') - bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE') - for edge in selected_edges: - edge.select = True - bpy.ops.mesh.mark_seam(clear=False) - - # Follow active quad unwrap for faces - bpy.ops.mesh.select_all(action='DESELECT') - bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE') - for face in selected_faces: - face.select = True - bm.faces.active = selected_faces[0] - - bpy.ops.uv.unwrap(method='ANGLE_BASED', margin=0.0226216) - bpy.ops.uv.select_all(action='SELECT') - bpy.ops.uv.textools_rectify(contextViewUV) - - # TODO: Restore initial selection - bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE') + bm = bmesh.from_edit_mesh(bpy.context.active_object.data) + uv_layers = bm.loops.layers.uv.verify() + + + contextViewUV = utilities_ui.GetContextViewUV() + if not contextViewUV: + self.report({'ERROR_INVALID_INPUT'}, "This tool requires an available UV/Image view.") + return + + # selected_initial = [edge for edge in bm.edges if edge.select] + selected_edges = [] + selected_faces = [] + + # Extend loop selection + bpy.ops.mesh.loop_multi_select(ring=False) + selected_edges = [edge for edge in bm.edges if edge.select] + + if len(selected_edges) == 0: + self.report({'ERROR_INVALID_INPUT'}, "No edges selected in the view" ) + return + + # Convert linked selection to single UV island + bpy.ops.mesh.select_linked(delimit=set()) + bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE') + bpy.ops.uv.textools_unwrap_faces_iron() + selected_faces = [face for face in bm.faces if face.select] + + if len(selected_faces) == 0: + self.report({'ERROR_INVALID_INPUT'}, "No faces available" ) + return + + # Mark previous selected edges as Seam + bpy.ops.mesh.select_all(action='DESELECT') + bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE') + for edge in selected_edges: + edge.select = True + bpy.ops.mesh.mark_seam(clear=False) + + # Follow active quad unwrap for faces + bpy.ops.mesh.select_all(action='DESELECT') + bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE') + for face in selected_faces: + face.select = True + bm.faces.active = selected_faces[0] + + bpy.ops.uv.unwrap(method='ANGLE_BASED', margin=0.0226216) + bpy.ops.uv.select_all(action='SELECT') + bpy.ops.uv.textools_rectify(contextViewUV) + + # TODO: Restore initial selection + bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE') bpy.utils.register_class(op) \ No newline at end of file diff --git a/op_unwrap_faces_iron.py b/op_unwrap_faces_iron.py index b324fea..c93d45a 100644 --- a/op_unwrap_faces_iron.py +++ b/op_unwrap_faces_iron.py @@ -8,69 +8,69 @@ from math import pi from . import utilities_uv class op(bpy.types.Operator): - """UV Operator description""" - bl_idname = "uv.textools_unwrap_faces_iron" - bl_label = "Iron" - bl_description = "Unwrap selected faces into a single UV island" - bl_options = {'REGISTER', 'UNDO'} - - @classmethod - def poll(cls, context): + """UV Operator description""" + bl_idname = "uv.textools_unwrap_faces_iron" + bl_label = "Iron" + bl_description = "Unwrap selected faces into a single UV island" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(cls, context): - if not bpy.context.active_object: - return False + if not bpy.context.active_object: + return False - if bpy.context.active_object.type != 'MESH': - return False + if bpy.context.active_object.type != 'MESH': + return False - #Only in Edit mode - if bpy.context.active_object.mode != 'EDIT': - return False + #Only in Edit mode + if bpy.context.active_object.mode != 'EDIT': + return False - # Need view Face mode - if tuple(bpy.context.scene.tool_settings.mesh_select_mode)[2] == False: - return False - #Only in UV editor mode - # if bpy.context.area.type != 'IMAGE_EDITOR': - # return False + # Need view Face mode + if tuple(bpy.context.scene.tool_settings.mesh_select_mode)[2] == False: + return False + #Only in UV editor mode + # if bpy.context.area.type != 'IMAGE_EDITOR': + # return False - #Requires UV map - # if not bpy.context.object.data.uv_layers: - # return False + #Requires UV map + # if not bpy.context.object.data.uv_layers: + # return False - # if bpy.context.scene.tool_settings.uv_select_mode != 'FACE': - # return False - return True + # if bpy.context.scene.tool_settings.uv_select_mode != 'FACE': + # return False + return True - def execute(self, context): - main(context) - return {'FINISHED'} + def execute(self, context): + main(context) + return {'FINISHED'} def main(context): - print("operatyor_faces_iron()") + print("operatyor_faces_iron()") - #Store selection - utilities_uv.selection_store() + #Store selection + utilities_uv.selection_store() - bm = bmesh.from_edit_mesh(bpy.context.active_object.data) - uv_layers = bm.loops.layers.uv.verify() + bm = bmesh.from_edit_mesh(bpy.context.active_object.data) + uv_layers = bm.loops.layers.uv.verify() - bpy.context.scene.tool_settings.uv_select_mode = 'FACE' - bpy.ops.mesh.mark_seam(clear=True) + bpy.context.scene.tool_settings.uv_select_mode = 'FACE' + bpy.ops.mesh.mark_seam(clear=True) - selected_faces = [f for f in bm.faces if f.select] + selected_faces = [f for f in bm.faces if f.select] - # Hard edges to seams - bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE') - bpy.ops.mesh.region_to_loop() - bpy.ops.mesh.mark_seam(clear=False) + # Hard edges to seams + bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE') + bpy.ops.mesh.region_to_loop() + bpy.ops.mesh.mark_seam(clear=False) - bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE') - for face in selected_faces: - face.select = True - - bpy.ops.uv.unwrap(method='ANGLE_BASED', margin=0.0226216) + bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE') + for face in selected_faces: + face.select = True + + bpy.ops.uv.unwrap(method='ANGLE_BASED', margin=0.0226216) bpy.utils.register_class(op) \ No newline at end of file diff --git a/op_uv_channel_add.py b/op_uv_channel_add.py index dad9635..59f3119 100644 --- a/op_uv_channel_add.py +++ b/op_uv_channel_add.py @@ -8,60 +8,60 @@ from math import pi from . import utilities_ui class op(bpy.types.Operator): - bl_idname = "uv.textools_uv_channel_add" - bl_label = "Add UV Channel" - bl_description = "Add a new UV channel with smart UV projected UV's and padding." - bl_options = {'REGISTER', 'UNDO'} + bl_idname = "uv.textools_uv_channel_add" + bl_label = "Add UV Channel" + bl_description = "Add a new UV channel with smart UV projected UV's and padding." + bl_options = {'REGISTER', 'UNDO'} - @classmethod - def poll(cls, context): - if bpy.context.active_object == None: - return False - if bpy.context.active_object.type != 'MESH': - return False - return True + @classmethod + def poll(cls, context): + if bpy.context.active_object == None: + return False + if bpy.context.active_object.type != 'MESH': + return False + return True - @classmethod - def poll(cls, context): - if bpy.context.active_object == None: - return False - if bpy.context.active_object.type != 'MESH': - return False - if len(bpy.context.selected_objects) != 1: - return False + @classmethod + def poll(cls, context): + if bpy.context.active_object == None: + return False + if bpy.context.active_object.type != 'MESH': + return False + if len(bpy.context.selected_objects) != 1: + return False - return True + return True - def execute(self, context): - print("Add UV") - - if len( bpy.context.object.data.uv_layers ) == 0: - # Create first UV channel - if bpy.context.active_object.mode != 'EDIT': - bpy.ops.object.mode_set(mode='EDIT') + def execute(self, context): + print("Add UV") + + if len( bpy.context.object.data.uv_layers ) == 0: + # Create first UV channel + if bpy.context.active_object.mode != 'EDIT': + bpy.ops.object.mode_set(mode='EDIT') - # Smart project UV's - bpy.ops.mesh.select_all(action='SELECT') - bpy.ops.uv.smart_project( - angle_limit=65, - island_margin=0.5, - user_area_weight=0, - use_aspect=True, - stretch_to_bounds=True - ) + # Smart project UV's + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.uv.smart_project( + angle_limit=65, + island_margin=0.5, + user_area_weight=0, + use_aspect=True, + stretch_to_bounds=True + ) - # Re-Apply padding as normalized values - bpy.ops.uv.select_all(action='SELECT') - bpy.ops.uv.pack_islands(margin=utilities_ui.get_padding()) - else: - # Add new UV channel based on last - bpy.ops.mesh.uv_texture_add() + # Re-Apply padding as normalized values + bpy.ops.uv.select_all(action='SELECT') + bpy.ops.uv.pack_islands(margin=utilities_ui.get_padding()) + else: + # Add new UV channel based on last + bpy.ops.mesh.uv_texture_add() - # Get current index - index = len(bpy.context.object.data.uv_layers)-1 - bpy.context.object.data.uv_layers.active_index = index - bpy.context.scene.texToolsSettings.uv_channel = str(index) - return {'FINISHED'} + # Get current index + index = len(bpy.context.object.data.uv_layers)-1 + bpy.context.object.data.uv_layers.active_index = index + bpy.context.scene.texToolsSettings.uv_channel = str(index) + return {'FINISHED'} bpy.utils.register_class(op) \ No newline at end of file diff --git a/op_uv_channel_swap.py b/op_uv_channel_swap.py index 68158e5..6877769 100644 --- a/op_uv_channel_swap.py +++ b/op_uv_channel_swap.py @@ -7,64 +7,64 @@ from math import pi class op(bpy.types.Operator): - bl_idname = "uv.textools_uv_channel_swap" - bl_label = "Move UV Channel" - bl_description = "Move UV channel up or down" - bl_options = {'REGISTER', 'UNDO'} + bl_idname = "uv.textools_uv_channel_swap" + bl_label = "Move UV Channel" + bl_description = "Move UV channel up or down" + bl_options = {'REGISTER', 'UNDO'} - is_down : bpy.props.BoolProperty(default=False) + is_down : bpy.props.BoolProperty(default=False) - @classmethod - def poll(cls, context): - if bpy.context.active_object == None: - return False - if bpy.context.active_object.type != 'MESH': - return False - if len(bpy.context.object.data.uv_layers) <= 1: - return False - return True + @classmethod + def poll(cls, context): + if bpy.context.active_object == None: + return False + if bpy.context.active_object.type != 'MESH': + return False + if len(bpy.context.object.data.uv_layers) <= 1: + return False + return True - def execute(self, context): - uv_layers = bpy.context.object.data.uv_layers + def execute(self, context): + uv_layers = bpy.context.object.data.uv_layers - if uv_layers.active_index == 0 and not self.is_down: - return {'FINISHED'} - elif uv_layers.active_index == len(uv_layers)-1 and self.is_down: - return {'FINISHED'} + if uv_layers.active_index == 0 and not self.is_down: + return {'FINISHED'} + elif uv_layers.active_index == len(uv_layers)-1 and self.is_down: + return {'FINISHED'} - def get_index(name): - return ([i for i in range(len(uv_layers)) if uv_layers[i].name == name])[0] + def get_index(name): + return ([i for i in range(len(uv_layers)) if uv_layers[i].name == name])[0] - def move_bottom(name): - # Set index - uv_layers.active_index = get_index(name) - # Copy (to bottom) - bpy.ops.mesh.uv_texture_add() - # Delete previous - uv_layers.active_index = get_index(name) - bpy.ops.mesh.uv_texture_remove() - # Rename new - uv_layers.active_index = len(uv_layers)-1 - uv_layers.active.name = name + def move_bottom(name): + # Set index + uv_layers.active_index = get_index(name) + # Copy (to bottom) + bpy.ops.mesh.uv_texture_add() + # Delete previous + uv_layers.active_index = get_index(name) + bpy.ops.mesh.uv_texture_remove() + # Rename new + uv_layers.active_index = len(uv_layers)-1 + uv_layers.active.name = name - count = len(uv_layers) + count = len(uv_layers) - index_A = uv_layers.active_index - index_B = index_A + (1 if self.is_down else -1) + index_A = uv_layers.active_index + index_B = index_A + (1 if self.is_down else -1) - if not self.is_down: - # Move up - for n in [uv_layers[i].name for i in range(index_B, count) if i != index_A]: - move_bottom(n) - bpy.context.scene.texToolsSettings.uv_channel = str(index_B) + if not self.is_down: + # Move up + for n in [uv_layers[i].name for i in range(index_B, count) if i != index_A]: + move_bottom(n) + bpy.context.scene.texToolsSettings.uv_channel = str(index_B) - elif self.is_down: - # Move down - for n in [uv_layers[i].name for i in range(index_A, count) if i != index_B]: - move_bottom(n) - bpy.context.scene.texToolsSettings.uv_channel = str(index_B) + elif self.is_down: + # Move down + for n in [uv_layers[i].name for i in range(index_A, count) if i != index_B]: + move_bottom(n) + bpy.context.scene.texToolsSettings.uv_channel = str(index_B) - return {'FINISHED'} + return {'FINISHED'} bpy.utils.register_class(op) \ No newline at end of file diff --git a/op_uv_crop.py b/op_uv_crop.py index 842cd50..91f47e4 100644 --- a/op_uv_crop.py +++ b/op_uv_crop.py @@ -9,58 +9,58 @@ from . import utilities_uv from . import utilities_ui class op(bpy.types.Operator): - bl_idname = "uv.textools_uv_crop" - bl_label = "Crop" - bl_description = "Crop UV area to selected UV faces" - bl_options = {'REGISTER', 'UNDO'} - - @classmethod - def poll(cls, context): - if not bpy.context.active_object: - return False - - if bpy.context.active_object.type != 'MESH': - return False + bl_idname = "uv.textools_uv_crop" + bl_label = "Crop" + bl_description = "Crop UV area to selected UV faces" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(cls, context): + if not bpy.context.active_object: + return False + + if bpy.context.active_object.type != 'MESH': + return False - #Only in Edit mode - if bpy.context.active_object.mode != 'EDIT': - return False + #Only in Edit mode + if bpy.context.active_object.mode != 'EDIT': + return False - #Only in UV editor mode - if bpy.context.area.type != 'IMAGE_EDITOR': - return False + #Only in UV editor mode + if bpy.context.area.type != 'IMAGE_EDITOR': + return False - #Requires UV map - if not bpy.context.object.data.uv_layers: - return False + #Requires UV map + if not bpy.context.object.data.uv_layers: + return False - return True - - def execute(self, context): - crop(self, context) - return {'FINISHED'} + return True + + def execute(self, context): + crop(self, context) + return {'FINISHED'} def crop(self, context): - bm = bmesh.from_edit_mesh(bpy.context.active_object.data); - uv_layers = bm.loops.layers.uv.verify(); + bm = bmesh.from_edit_mesh(bpy.context.active_object.data); + uv_layers = bm.loops.layers.uv.verify(); - padding = utilities_ui.get_padding() + padding = utilities_ui.get_padding() - - # Scale to fit bounds - bbox = utilities_uv.getSelectionBBox() - scale_u = (1.0-padding) / bbox['width'] - scale_v = (1.0-padding) / bbox['height'] - scale = min(scale_u, scale_v) + + # Scale to fit bounds + bbox = utilities_uv.getSelectionBBox() + scale_u = (1.0-padding) / bbox['width'] + scale_v = (1.0-padding) / bbox['height'] + scale = min(scale_u, scale_v) - bpy.ops.transform.resize(value=(scale, scale, scale), constraint_axis=(False, False, False), mirror=False, use_proportional_edit=False) + bpy.ops.transform.resize(value=(scale, scale, scale), constraint_axis=(False, False, False), mirror=False, use_proportional_edit=False) - # Reposition - bbox = utilities_uv.getSelectionBBox() + # Reposition + bbox = utilities_uv.getSelectionBBox() - delta_position = Vector((padding/2,1-padding/2)) - Vector((bbox['min'].x, bbox['min'].y + bbox['height'])) - bpy.ops.transform.translate(value=(delta_position.x, delta_position.y, 0)) + delta_position = Vector((padding/2,1-padding/2)) - Vector((bbox['min'].x, bbox['min'].y + bbox['height'])) + bpy.ops.transform.translate(value=(delta_position.x, delta_position.y, 0)) bpy.utils.register_class(op) \ No newline at end of file diff --git a/op_uv_fill.py b/op_uv_fill.py index 683bda4..ad17b06 100644 --- a/op_uv_fill.py +++ b/op_uv_fill.py @@ -11,107 +11,107 @@ from . import utilities_uv from . import utilities_ui class op(bpy.types.Operator): - bl_idname = "uv.textools_uv_fill" - bl_label = "Fill" - bl_description = "Fill UV selection to UV canvas" - bl_options = {'REGISTER', 'UNDO'} - - @classmethod - def poll(cls, context): - if not bpy.context.active_object: - return False - - if bpy.context.active_object.type != 'MESH': - return False - - #Only in Edit mode - if bpy.context.active_object.mode != 'EDIT': - return False - - #Only in UV editor mode - if bpy.context.area.type != 'IMAGE_EDITOR': - return False - - #Requires UV map - if not bpy.context.object.data.uv_layers: - return False - - return True - - def execute(self, context): - fill(self, context) - return {'FINISHED'} + bl_idname = "uv.textools_uv_fill" + bl_label = "Fill" + bl_description = "Fill UV selection to UV canvas" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(cls, context): + if not bpy.context.active_object: + return False + + if bpy.context.active_object.type != 'MESH': + return False + + #Only in Edit mode + if bpy.context.active_object.mode != 'EDIT': + return False + + #Only in UV editor mode + if bpy.context.area.type != 'IMAGE_EDITOR': + return False + + #Requires UV map + if not bpy.context.object.data.uv_layers: + return False + + return True + + def execute(self, context): + fill(self, context) + return {'FINISHED'} def fill(self, context): - #Store selection - utilities_uv.selection_store() + #Store selection + utilities_uv.selection_store() - bm = bmesh.from_edit_mesh(bpy.context.active_object.data); - uv_layers = bm.loops.layers.uv.verify(); + bm = bmesh.from_edit_mesh(bpy.context.active_object.data); + uv_layers = bm.loops.layers.uv.verify(); - - # 1.) Rotate minimal bounds (less than 45 degrees rotation) - steps = 8 - angle = 45; # Starting Angle, half each step - bboxPrevious = utilities_uv.getSelectionBBox() - - for i in range(0, steps): - # Rotate right - bpy.ops.transform.rotate(value=(angle * math.pi / 180), orient_axis='Z') - bbox = utilities_uv.getSelectionBBox() + + # 1.) Rotate minimal bounds (less than 45 degrees rotation) + steps = 8 + angle = 45; # Starting Angle, half each step + bboxPrevious = utilities_uv.getSelectionBBox() + + for i in range(0, steps): + # Rotate right + bpy.ops.transform.rotate(value=(angle * math.pi / 180), orient_axis='Z') + bbox = utilities_uv.getSelectionBBox() - print("Rotate {}, diff le: {}".format(angle, bbox['height'] - bboxPrevious['height'])) + print("Rotate {}, diff le: {}".format(angle, bbox['height'] - bboxPrevious['height'])) - if i == 0: - # Check if already squared - sizeA = bboxPrevious['width'] * bboxPrevious['height'] - sizeB = bbox['width'] * bbox['height'] - if abs(bbox['width'] - bbox['height']) <= 0.0001 and sizeA < sizeB: - bpy.ops.transform.rotate(value=(-angle * math.pi / 180), orient_axis='Z') - break; + if i == 0: + # Check if already squared + sizeA = bboxPrevious['width'] * bboxPrevious['height'] + sizeB = bbox['width'] * bbox['height'] + if abs(bbox['width'] - bbox['height']) <= 0.0001 and sizeA < sizeB: + bpy.ops.transform.rotate(value=(-angle * math.pi / 180), orient_axis='Z') + break; - if bbox['minLength'] < bboxPrevious['minLength']: - bboxPrevious = bbox; # Success - else: - # Rotate Left - bpy.ops.transform.rotate(value=(-angle*2 * math.pi / 180), orient_axis='Z') - bbox = utilities_uv.getSelectionBBox() - if bbox['minLength'] < bboxPrevious['minLength']: - bboxPrevious = bbox; # Success - else: - # Restore angle of this iteration - bpy.ops.transform.rotate(value=(angle * math.pi / 180), orient_axis='Z') + if bbox['minLength'] < bboxPrevious['minLength']: + bboxPrevious = bbox; # Success + else: + # Rotate Left + bpy.ops.transform.rotate(value=(-angle*2 * math.pi / 180), orient_axis='Z') + bbox = utilities_uv.getSelectionBBox() + if bbox['minLength'] < bboxPrevious['minLength']: + bboxPrevious = bbox; # Success + else: + # Restore angle of this iteration + bpy.ops.transform.rotate(value=(angle * math.pi / 180), orient_axis='Z') - angle = angle / 2 + angle = angle / 2 - if bboxPrevious['width'] < bboxPrevious['height']: - bpy.ops.transform.rotate(value=(90 * math.pi / 180), orient_axis='Z') + if bboxPrevious['width'] < bboxPrevious['height']: + bpy.ops.transform.rotate(value=(90 * math.pi / 180), orient_axis='Z') - # 2.) Match width and height to UV bounds - bbox = utilities_uv.getSelectionBBox() + # 2.) Match width and height to UV bounds + bbox = utilities_uv.getSelectionBBox() - scale_x = 1.0 / bbox['width'] - scale_y = 1.0 / bbox['height'] - - print("Scale {} | {}".format(scale_x, scale_y)) + scale_x = 1.0 / bbox['width'] + scale_y = 1.0 / bbox['height'] + + print("Scale {} | {}".format(scale_x, scale_y)) - bpy.context.tool_settings.transform_pivot_point = 'BOUNDING_BOX_CENTER' - bpy.ops.transform.resize(value=(scale_x, scale_y, 1), constraint_axis=(False, False, False), orient_type='GLOBAL', use_proportional_edit=False) + bpy.context.tool_settings.transform_pivot_point = 'BOUNDING_BOX_CENTER' + bpy.ops.transform.resize(value=(scale_x, scale_y, 1), constraint_axis=(False, False, False), orient_type='GLOBAL', use_proportional_edit=False) - bbox = utilities_uv.getSelectionBBox() - offset_x = -bbox['min'].x - offset_y = -bbox['min'].y - - bpy.ops.transform.translate(value=(offset_x, offset_y, 0), constraint_axis=(False, False, False), orient_type='GLOBAL', use_proportional_edit=False) + bbox = utilities_uv.getSelectionBBox() + offset_x = -bbox['min'].x + offset_y = -bbox['min'].y + + bpy.ops.transform.translate(value=(offset_x, offset_y, 0), constraint_axis=(False, False, False), orient_type='GLOBAL', use_proportional_edit=False) - #Restore selection - utilities_uv.selection_restore() + #Restore selection + utilities_uv.selection_restore() bpy.utils.register_class(op) \ No newline at end of file diff --git a/op_uv_resize.py b/op_uv_resize.py index 39b4138..b4461e2 100644 --- a/op_uv_resize.py +++ b/op_uv_resize.py @@ -20,244 +20,244 @@ utilities_ui.icon_register("op_extend_canvas_BR_active.png") def on_dropdown_size_x(self, context): - self.size_x = int(self.dropdown_size_x) - # context.area.tag_redraw() + self.size_x = int(self.dropdown_size_x) + # context.area.tag_redraw() def on_dropdown_size_y(self, context): - self.size_y = int(self.dropdown_size_y) - # context.area.tag_redraw() + self.size_y = int(self.dropdown_size_y) + # context.area.tag_redraw() class op(bpy.types.Operator): - bl_idname = "uv.textools_uv_resize" - bl_label = "Resize Area" - bl_description = "Resize or extend the UV area" - bl_options = {'REGISTER', 'UNDO'} - - size_x : bpy.props.IntProperty( - name = "Width", - description="padding size in pixels", - default = 1024, - min = 1, - max = 8192 - ) - size_y : bpy.props.IntProperty( - name = "Height", - description="padding size in pixels", - default = 1024, - min = 1, - max = 8192 - ) - dropdown_size_x : bpy.props.EnumProperty( - items = utilities_ui.size_textures, - name = "", - update = on_dropdown_size_x, - default = '1024' - ) - dropdown_size_y : bpy.props.EnumProperty( - items = utilities_ui.size_textures, - name = "", - update = on_dropdown_size_y, - default = '1024' - ) - - direction : bpy.props.EnumProperty(name='direction', items=( - ('TL',' ','Top Left', utilities_ui.icon_get("op_extend_canvas_TL_active"),0), - ('BL',' ','Bottom Left', utilities_ui.icon_get("op_extend_canvas_BL_active"),2), - ('TR',' ','Top Right', utilities_ui.icon_get("op_extend_canvas_TR_active"),1), - ('BR',' ','Bottom Right', utilities_ui.icon_get("op_extend_canvas_BR_active"),3) - )) - - @classmethod - def poll(cls, context): - if not bpy.context.active_object: - return False - - if bpy.context.active_object.type != 'MESH': - return False - - #Only in Edit mode - if bpy.context.active_object.mode != 'EDIT': - return False - - #Only in UV editor mode - if bpy.context.area.type != 'IMAGE_EDITOR': - return False - - #Requires UV map - if not bpy.context.object.data.uv_layers: - return False - - return True - - - def invoke(self, context, event): - print("Invoke resize area") - self.size_x = bpy.context.scene.texToolsSettings.size[0] - self.size_y = bpy.context.scene.texToolsSettings.size[1] - - for item in utilities_ui.size_textures: - if int(item[0]) == self.size_x: - self.dropdown_size_x = item[0] - break - for item in utilities_ui.size_textures: - if int(item[0]) == self.size_y: - self.dropdown_size_y = item[0] - break - - - return context.window_manager.invoke_props_dialog(self, width = 140) - - def check(self, context): - return True - - def draw(self, context): - # https://b3d.interplanety.org/en/creating-pop-up-panels-with-user-ui-in-blender-add-on/ - layout = self.layout - - - layout.separator() - - # New Size - row = layout.row() - split = row.split(factor=0.6) - c = split.column(align=True) - c.prop(self, "size_x", text="X",expand=True) - c.prop(self, "size_y", text="Y",expand=True) - - c = split.column(align=True) - c.prop(self, "dropdown_size_x", text="") - c.prop(self, "dropdown_size_y", text="") - - # Direction - col = layout.column(align=True) - col.label(text="Direction") - row = col.row(align=True) - row.prop(self,'direction', expand=True) - - # Summary - size_A = "{} x {}".format(bpy.context.scene.texToolsSettings.size[0], bpy.context.scene.texToolsSettings.size[1]) - if bpy.context.scene.texToolsSettings.size[0] == bpy.context.scene.texToolsSettings.size[1]: - size_A = "{}²".format(bpy.context.scene.texToolsSettings.size[0]) - size_B = "{} x {}".format(self.size_x, self.size_y) - if self.size_x == self.size_y: - size_B = "{}²".format(self.size_x) - - layout.label(text="{} to {}".format( - size_A, size_B - )) - - - layout.separator() - - - def execute(self, context): - - #Store selection - utilities_uv.selection_store() - - # Get start and end size - size_A = Vector([ - bpy.context.scene.texToolsSettings.size[0], - bpy.context.scene.texToolsSettings.size[1] - ]) - size_B = Vector([ - self.size_x, - self.size_y - ]) - - resize_uv( - self, - context, - self.direction, - size_A, - size_B - ) - resize_image( - context, - self.direction, - size_A, - size_B - ) + bl_idname = "uv.textools_uv_resize" + bl_label = "Resize Area" + bl_description = "Resize or extend the UV area" + bl_options = {'REGISTER', 'UNDO'} + + size_x : bpy.props.IntProperty( + name = "Width", + description="padding size in pixels", + default = 1024, + min = 1, + max = 8192 + ) + size_y : bpy.props.IntProperty( + name = "Height", + description="padding size in pixels", + default = 1024, + min = 1, + max = 8192 + ) + dropdown_size_x : bpy.props.EnumProperty( + items = utilities_ui.size_textures, + name = "", + update = on_dropdown_size_x, + default = '1024' + ) + dropdown_size_y : bpy.props.EnumProperty( + items = utilities_ui.size_textures, + name = "", + update = on_dropdown_size_y, + default = '1024' + ) + + direction : bpy.props.EnumProperty(name='direction', items=( + ('TL',' ','Top Left', utilities_ui.icon_get("op_extend_canvas_TL_active"),0), + ('BL',' ','Bottom Left', utilities_ui.icon_get("op_extend_canvas_BL_active"),2), + ('TR',' ','Top Right', utilities_ui.icon_get("op_extend_canvas_TR_active"),1), + ('BR',' ','Bottom Right', utilities_ui.icon_get("op_extend_canvas_BR_active"),3) + )) + + @classmethod + def poll(cls, context): + if not bpy.context.active_object: + return False + + if bpy.context.active_object.type != 'MESH': + return False + + #Only in Edit mode + if bpy.context.active_object.mode != 'EDIT': + return False + + #Only in UV editor mode + if bpy.context.area.type != 'IMAGE_EDITOR': + return False + + #Requires UV map + if not bpy.context.object.data.uv_layers: + return False + + return True + + + def invoke(self, context, event): + print("Invoke resize area") + self.size_x = bpy.context.scene.texToolsSettings.size[0] + self.size_y = bpy.context.scene.texToolsSettings.size[1] + + for item in utilities_ui.size_textures: + if int(item[0]) == self.size_x: + self.dropdown_size_x = item[0] + break + for item in utilities_ui.size_textures: + if int(item[0]) == self.size_y: + self.dropdown_size_y = item[0] + break + + + return context.window_manager.invoke_props_dialog(self, width = 140) + + def check(self, context): + return True + + def draw(self, context): + # https://b3d.interplanety.org/en/creating-pop-up-panels-with-user-ui-in-blender-add-on/ + layout = self.layout + + + layout.separator() + + # New Size + row = layout.row() + split = row.split(factor=0.6) + c = split.column(align=True) + c.prop(self, "size_x", text="X",expand=True) + c.prop(self, "size_y", text="Y",expand=True) + + c = split.column(align=True) + c.prop(self, "dropdown_size_x", text="") + c.prop(self, "dropdown_size_y", text="") + + # Direction + col = layout.column(align=True) + col.label(text="Direction") + row = col.row(align=True) + row.prop(self,'direction', expand=True) + + # Summary + size_A = "{} x {}".format(bpy.context.scene.texToolsSettings.size[0], bpy.context.scene.texToolsSettings.size[1]) + if bpy.context.scene.texToolsSettings.size[0] == bpy.context.scene.texToolsSettings.size[1]: + size_A = "{}²".format(bpy.context.scene.texToolsSettings.size[0]) + size_B = "{} x {}".format(self.size_x, self.size_y) + if self.size_x == self.size_y: + size_B = "{}²".format(self.size_x) + + layout.label(text="{} to {}".format( + size_A, size_B + )) + + + layout.separator() + + + def execute(self, context): + + #Store selection + utilities_uv.selection_store() + + # Get start and end size + size_A = Vector([ + bpy.context.scene.texToolsSettings.size[0], + bpy.context.scene.texToolsSettings.size[1] + ]) + size_B = Vector([ + self.size_x, + self.size_y + ]) + + resize_uv( + self, + context, + self.direction, + size_A, + size_B + ) + resize_image( + context, + self.direction, + size_A, + size_B + ) - bpy.context.scene.texToolsSettings.size[0] = self.size_x - bpy.context.scene.texToolsSettings.size[1] = self.size_y + bpy.context.scene.texToolsSettings.size[0] = self.size_x + bpy.context.scene.texToolsSettings.size[1] = self.size_y - #Restore selection - utilities_uv.selection_restore() + #Restore selection + utilities_uv.selection_restore() - return {'FINISHED'} + return {'FINISHED'} def resize_uv(self, context, mode, size_A, size_B): - # Set pivot - bpy.context.tool_settings.transform_pivot_point = 'CURSOR' - if mode == 'TL': - bpy.ops.uv.cursor_set(location=Vector([0,1])) - elif mode == 'TR': - bpy.ops.uv.cursor_set(location=Vector([1,1])) - elif mode == 'BL': - bpy.ops.uv.cursor_set(location=Vector([0,0])) - elif mode == 'BR': - bpy.ops.uv.cursor_set(location=Vector([1,0])) + # Set pivot + bpy.context.tool_settings.transform_pivot_point = 'CURSOR' + if mode == 'TL': + bpy.ops.uv.cursor_set(location=Vector([0,1])) + elif mode == 'TR': + bpy.ops.uv.cursor_set(location=Vector([1,1])) + elif mode == 'BL': + bpy.ops.uv.cursor_set(location=Vector([0,0])) + elif mode == 'BR': + bpy.ops.uv.cursor_set(location=Vector([1,0])) - # Select all UV faces - bpy.ops.uv.select_all(action='SELECT') + # Select all UV faces + bpy.ops.uv.select_all(action='SELECT') - # Resize - scale_x = size_A.x / size_B.x - scale_y = size_A.y / size_B.y - bpy.ops.transform.resize(value=(scale_x, scale_y, 1.0), use_proportional_edit=False) + # Resize + scale_x = size_A.x / size_B.x + scale_y = size_A.y / size_B.y + bpy.ops.transform.resize(value=(scale_x, scale_y, 1.0), use_proportional_edit=False) def resize_image(context, mode, size_A, size_B): - print("resize image {}".format( context.area.spaces )) - - # Notes: https://blender.stackexchange.com/questions/31514/active-image-of-uv-image-editor - # https://docs.blender.org/api/blender_python_api_2_70_4/bpy.types.SpaceImageEditor.html - - - if context.area.spaces.active != None: - if context.area.spaces.active.image != None: - image = context.area.spaces.active.image - image_obj = utilities_texel.get_object_texture_image(bpy.context.active_object) - if name_texture in image.name or image == image_obj: - # Resize Image UV editor background image - utilities_texel.image_resize(image, int(size_B.x), int(size_B.y)) - - else: - # No Image assigned - - # Get background color from theme + 1.25x brighter - theme = bpy.context.preferences.themes[0] - color = theme.image_editor.space.back.copy() - color.r*= 1.15 - color.g*= 1.15 - color.b*= 1.15 - - image = None - if name_texture in bpy.data.images: - # TexTools Image already exists - image = bpy.data.images[name_texture] - image.scale( int(size_B.x), int(size_B.y) ) - image.generated_width = int(size_B.x) - image.generated_height = int(size_B.y) - else: - # Create new image - image = bpy.data.images.new(name_texture, width=int(size_B.x), height=int(size_B.y)) - image.generated_color = (color.r, color.g, color.b, 1.0) - image.generated_type = 'BLANK' - image.generated_width = int(size_B.x) - image.generated_height = int(size_B.y) - - # Assign in UV view - context.area.spaces.active.image = image - - # Clean up images and materials - utilities_texel.checker_images_cleanup() + print("resize image {}".format( context.area.spaces )) + + # Notes: https://blender.stackexchange.com/questions/31514/active-image-of-uv-image-editor + # https://docs.blender.org/api/blender_python_api_2_70_4/bpy.types.SpaceImageEditor.html + + + if context.area.spaces.active != None: + if context.area.spaces.active.image != None: + image = context.area.spaces.active.image + image_obj = utilities_texel.get_object_texture_image(bpy.context.active_object) + if name_texture in image.name or image == image_obj: + # Resize Image UV editor background image + utilities_texel.image_resize(image, int(size_B.x), int(size_B.y)) + + else: + # No Image assigned + + # Get background color from theme + 1.25x brighter + theme = bpy.context.preferences.themes[0] + color = theme.image_editor.space.back.copy() + color.r*= 1.15 + color.g*= 1.15 + color.b*= 1.15 + + image = None + if name_texture in bpy.data.images: + # TexTools Image already exists + image = bpy.data.images[name_texture] + image.scale( int(size_B.x), int(size_B.y) ) + image.generated_width = int(size_B.x) + image.generated_height = int(size_B.y) + else: + # Create new image + image = bpy.data.images.new(name_texture, width=int(size_B.x), height=int(size_B.y)) + image.generated_color = (color.r, color.g, color.b, 1.0) + image.generated_type = 'BLANK' + image.generated_width = int(size_B.x) + image.generated_height = int(size_B.y) + + # Assign in UV view + context.area.spaces.active.image = image + + # Clean up images and materials + utilities_texel.checker_images_cleanup() diff --git a/op_uv_size_get.py b/op_uv_size_get.py index b19a1ec..70b68e8 100644 --- a/op_uv_size_get.py +++ b/op_uv_size_get.py @@ -8,37 +8,37 @@ from math import pi from . import utilities_texel class op(bpy.types.Operator): - bl_idname = "uv.textools_uv_size_get" - bl_label = "Get Size" - bl_description = "Get selected object's texture size" + bl_idname = "uv.textools_uv_size_get" + bl_label = "Get Size" + bl_description = "Get selected object's texture size" - @classmethod - def poll(cls, context): - if not bpy.context.active_object: - return False - - if bpy.context.active_object not in bpy.context.selected_objects: - return False + @classmethod + def poll(cls, context): + if not bpy.context.active_object: + return False + + if bpy.context.active_object not in bpy.context.selected_objects: + return False - if bpy.context.active_object.type != 'MESH': - return False + if bpy.context.active_object.type != 'MESH': + return False - return True - - def execute(self, context): - get_size(self, context) - return {'FINISHED'} + return True + + def execute(self, context): + get_size(self, context) + return {'FINISHED'} def get_size(self, context): - image = utilities_texel.get_object_texture_image (bpy.context.active_object) + image = utilities_texel.get_object_texture_image (bpy.context.active_object) - if not image: - self.report({'ERROR_INVALID_INPUT'}, "No Texture found on selected object" ) - return + if not image: + self.report({'ERROR_INVALID_INPUT'}, "No Texture found on selected object" ) + return - bpy.context.scene.texToolsSettings.size[0] = image.size[0] - bpy.context.scene.texToolsSettings.size[1] = image.size[1] + bpy.context.scene.texToolsSettings.size[0] = image.size[0] + bpy.context.scene.texToolsSettings.size[1] = image.size[1] bpy.utils.register_class(op) \ No newline at end of file diff --git a/utilities_bake.py b/utilities_bake.py index a371f42..8e3e1f1 100644 --- a/utilities_bake.py +++ b/utilities_bake.py @@ -22,584 +22,584 @@ split_chars = [' ','_','.','-'] class BakeMode: - material = "" #Material name from external blend file - type = 'EMIT' - normal_space = 'TANGENT' - setVColor = None #Set Vertex color method - color = (0.23, 0.23, 0.23, 1) #Background color - engine = 'CYCLES' #render engine, by default CYCLES - composite = None #use composite scene to process end result - use_project = False #Bake projected? - params = [] #UI Parameters from scene settings - - def __init__(self, material="", type='EMIT', normal_space='TANGENT', setVColor=None, color= (0.23, 0.23, 0.23, 1), engine='CYCLES', params = [], composite=None, use_project=False): - self.material = material - self.type = type - self.normal_space = normal_space - self.setVColor = setVColor - self.color = color - self.engine = engine - self.params = params - self.composite = composite - self.use_project = use_project + material = "" #Material name from external blend file + type = 'EMIT' + normal_space = 'TANGENT' + setVColor = None #Set Vertex color method + color = (0.23, 0.23, 0.23, 1) #Background color + engine = 'CYCLES' #render engine, by default CYCLES + composite = None #use composite scene to process end result + use_project = False #Bake projected? + params = [] #UI Parameters from scene settings + + def __init__(self, material="", type='EMIT', normal_space='TANGENT', setVColor=None, color= (0.23, 0.23, 0.23, 1), engine='CYCLES', params = [], composite=None, use_project=False): + self.material = material + self.type = type + self.normal_space = normal_space + self.setVColor = setVColor + self.color = color + self.engine = engine + self.params = params + self.composite = composite + self.use_project = use_project def on_select_bake_mode(mode): - print("Mode changed {}".format(mode)) + print("Mode changed {}".format(mode)) - if len(settings.sets) > 0: - name_texture = "{}_{}".format(settings.sets[0].name, mode) + if len(settings.sets) > 0: + name_texture = "{}_{}".format(settings.sets[0].name, mode) - if name_texture in bpy.data.images: - image = bpy.data.images[name_texture] + if name_texture in bpy.data.images: + image = bpy.data.images[name_texture] - # Set background image - for area in bpy.context.screen.areas: - if area.type == 'IMAGE_EDITOR': - area.spaces[0].image = image + # Set background image + for area in bpy.context.screen.areas: + if area.type == 'IMAGE_EDITOR': + area.spaces[0].image = image def store_bake_settings(): - # Render Settings - settings.bake_render_engine = bpy.context.scene.render.engine - settings.bake_cycles_samples = bpy.context.scene.cycles.samples + # Render Settings + settings.bake_render_engine = bpy.context.scene.render.engine + settings.bake_cycles_samples = bpy.context.scene.cycles.samples - # Disable Objects that are meant to be hidden - sets = settings.sets - objects_sets = [] - for set in sets: - for obj in set.objects_low: - if obj not in objects_sets: - objects_sets.append(obj) - for obj in set.objects_high: - if obj not in objects_sets: - objects_sets.append(obj) - for obj in set.objects_cage: - if obj not in objects_sets: - objects_sets.append(obj) + # Disable Objects that are meant to be hidden + sets = settings.sets + objects_sets = [] + for set in sets: + for obj in set.objects_low: + if obj not in objects_sets: + objects_sets.append(obj) + for obj in set.objects_high: + if obj not in objects_sets: + objects_sets.append(obj) + for obj in set.objects_cage: + if obj not in objects_sets: + objects_sets.append(obj) - settings.bake_objects_hide_render = [] + settings.bake_objects_hide_render = [] - # for obj in bpy.context.view_layer.objects: - # if obj.hide_render == False and obj not in objects_sets: - # Check if layer is active: - # for l in range(0, len(obj.layers)): - # if obj.layers[l] and bpy.context.scene.layers[l]: - # settings.bake_objects_hide_render.append(obj) - # break #sav + # for obj in bpy.context.view_layer.objects: + # if obj.hide_render == False and obj not in objects_sets: + # Check if layer is active: + # for l in range(0, len(obj.layers)): + # if obj.layers[l] and bpy.context.scene.layers[l]: + # settings.bake_objects_hide_render.append(obj) + # break #sav - for obj in settings.bake_objects_hide_render: - obj.hide_render = True - # obj.cycles_visibility.shadow = False + for obj in settings.bake_objects_hide_render: + obj.hide_render = True + # obj.cycles_visibility.shadow = False def restore_bake_settings(): - # Render Settings - if settings.bake_render_engine != '': - bpy.context.scene.render.engine = settings.bake_render_engine + # Render Settings + if settings.bake_render_engine != '': + bpy.context.scene.render.engine = settings.bake_render_engine - bpy.context.scene.cycles.samples = settings.bake_cycles_samples + bpy.context.scene.cycles.samples = settings.bake_cycles_samples - # Restore Objects that were hidden for baking - for obj in settings.bake_objects_hide_render: - if obj: - obj.hide_render = False - # obj.cycles_visibility.shadow = True + # Restore Objects that were hidden for baking + for obj in settings.bake_objects_hide_render: + if obj: + obj.hide_render = False + # obj.cycles_visibility.shadow = True stored_materials = {} stored_material_faces = {} def store_materials_clear(): - stored_materials.clear() - stored_material_faces.clear() + stored_materials.clear() + stored_material_faces.clear() def store_materials(obj): - stored_materials[obj] = [] - stored_material_faces[obj] = [] + stored_materials[obj] = [] + stored_material_faces[obj] = [] - # Enter edit mode - bpy.ops.object.select_all(action='DESELECT') - obj.select_set( state = True, view_layer = None) - bpy.context.view_layer.objects.active = obj + # Enter edit mode + bpy.ops.object.select_all(action='DESELECT') + obj.select_set( state = True, view_layer = None) + bpy.context.view_layer.objects.active = obj - bpy.ops.object.mode_set(mode='EDIT') - bm = bmesh.from_edit_mesh(obj.data); + bpy.ops.object.mode_set(mode='EDIT') + bm = bmesh.from_edit_mesh(obj.data); - # for each slot backup the material - for s in range(len(obj.material_slots)): - slot = obj.material_slots[s] + # for each slot backup the material + for s in range(len(obj.material_slots)): + slot = obj.material_slots[s] - stored_materials[obj].append(slot.material) - stored_material_faces[obj].append( [face.index for face in bm.faces if face.material_index == s] ) - - # print("Faces: {}x".format( len(stored_material_faces[obj][-1]) )) + stored_materials[obj].append(slot.material) + stored_material_faces[obj].append( [face.index for face in bm.faces if face.material_index == s] ) + + # print("Faces: {}x".format( len(stored_material_faces[obj][-1]) )) - if slot and slot.material: - slot.material.name = "backup_"+slot.material.name - print("- Store {} = {}".format(obj.name,slot.material.name)) + if slot and slot.material: + slot.material.name = "backup_"+slot.material.name + print("- Store {} = {}".format(obj.name,slot.material.name)) - # Back to object mode - bpy.ops.object.mode_set(mode='OBJECT') + # Back to object mode + bpy.ops.object.mode_set(mode='OBJECT') def restore_materials(): - for obj in stored_materials: - # Enter edit mode - bpy.context.view_layer.objects.active = obj - bpy.ops.object.mode_set(mode='EDIT') - bm = bmesh.from_edit_mesh(obj.data); + for obj in stored_materials: + # Enter edit mode + bpy.context.view_layer.objects.active = obj + bpy.ops.object.mode_set(mode='EDIT') + bm = bmesh.from_edit_mesh(obj.data); - # Restore slots - for index in range(len(stored_materials[obj])): - material = stored_materials[obj][index] - faces = stored_material_faces[obj][index] - - if material: - material.name = material.name.replace("backup_","") - obj.material_slots[index].material = material + # Restore slots + for index in range(len(stored_materials[obj])): + material = stored_materials[obj][index] + faces = stored_material_faces[obj][index] + + if material: + material.name = material.name.replace("backup_","") + obj.material_slots[index].material = material - # Face material indexies - for face in bm.faces: - if face.index in faces: - face.material_index = index + # Face material indexies + for face in bm.faces: + if face.index in faces: + face.material_index = index - # Back to object mode - bpy.ops.object.mode_set(mode='OBJECT') + # Back to object mode + bpy.ops.object.mode_set(mode='OBJECT') - # Remove material slots if none before - if len(stored_materials[obj]) == 0: - for i in range(len(obj.material_slots)): - bpy.ops.object.material_slot_remove() + # Remove material slots if none before + if len(stored_materials[obj]) == 0: + for i in range(len(obj.material_slots)): + bpy.ops.object.material_slot_remove() def get_set_name_base(obj): - def remove_digits(name): - # Remove blender naming digits, e.g. cube.001, cube.002,... - if len(name)>= 4 and name[-4] == '.' and name[-3].isdigit() and name[-2].isdigit() and name[-1].isdigit(): - return name[:-4] - return name + def remove_digits(name): + # Remove blender naming digits, e.g. cube.001, cube.002,... + if len(name)>= 4 and name[-4] == '.' and name[-3].isdigit() and name[-2].isdigit() and name[-1].isdigit(): + return name[:-4] + return name - # Reference parent as base name - if obj.parent and obj.parent in bpy.context.selected_objects: - return remove_digits(obj.parent.name).lower() + # Reference parent as base name + if obj.parent and obj.parent in bpy.context.selected_objects: + return remove_digits(obj.parent.name).lower() - # Reference group name as base name - elif len(obj.users_collection) == 2: - return remove_digits(obj.users_collection[0].name).lower() + # Reference group name as base name + elif len(obj.users_collection) == 2: + return remove_digits(obj.users_collection[0].name).lower() - # Use Object name - else: - return remove_digits(obj.name).lower() + # Use Object name + else: + return remove_digits(obj.name).lower() def get_set_name(obj): - # Get Basic name - name = get_set_name_base(obj) - - # Split by ' ','_','.' etc. - split = name.lower() - for char in split_chars: - split = split.replace(char,' ') - strings = split.split(' ') - - # Remove all keys from name - keys = keywords_cage + keywords_high + keywords_low + keywords_float - new_strings = [] - for string in strings: - is_found = False - for key in keys: - if string == key: - is_found = True - break - if not is_found: - new_strings.append(string) - elif len(new_strings) > 0: - # No more strings once key is found if we have already something - break - - return "_".join(new_strings) + # Get Basic name + name = get_set_name_base(obj) + + # Split by ' ','_','.' etc. + split = name.lower() + for char in split_chars: + split = split.replace(char,' ') + strings = split.split(' ') + + # Remove all keys from name + keys = keywords_cage + keywords_high + keywords_low + keywords_float + new_strings = [] + for string in strings: + is_found = False + for key in keys: + if string == key: + is_found = True + break + if not is_found: + new_strings.append(string) + elif len(new_strings) > 0: + # No more strings once key is found if we have already something + break + + return "_".join(new_strings) def get_object_type(obj): - name = get_set_name_base(obj) + name = get_set_name_base(obj) - # Detect by name pattern - split = name.lower() - for char in split_chars: - split = split.replace(char,' ') - strings = split.split(' ') + # Detect by name pattern + split = name.lower() + for char in split_chars: + split = split.replace(char,' ') + strings = split.split(' ') - # Detect float, more rare than low - for string in strings: - for key in keywords_float: - if key == string: - return 'float' + # Detect float, more rare than low + for string in strings: + for key in keywords_float: + if key == string: + return 'float' - # Detect by modifiers (Only if more than 1 object selected) - if len(bpy.context.selected_objects) > 1: - if obj.modifiers: - for modifier in obj.modifiers: - if modifier.type == 'SUBSURF' and modifier.render_levels > 0: - return 'high' - elif modifier.type == 'BEVEL': - return 'high' + # Detect by modifiers (Only if more than 1 object selected) + if len(bpy.context.selected_objects) > 1: + if obj.modifiers: + for modifier in obj.modifiers: + if modifier.type == 'SUBSURF' and modifier.render_levels > 0: + return 'high' + elif modifier.type == 'BEVEL': + return 'high' - # Detect High first, more rare - for string in strings: - for key in keywords_high: - if key == string: - return 'high' - - # Detect cage, more rare than low - for string in strings: - for key in keywords_cage: - if key == string: - return 'cage' + # Detect High first, more rare + for string in strings: + for key in keywords_high: + if key == string: + return 'high' + + # Detect cage, more rare than low + for string in strings: + for key in keywords_cage: + if key == string: + return 'cage' - + - # Detect low - for string in strings: - for key in keywords_low: - if key == string: - return 'low' + # Detect low + for string in strings: + for key in keywords_low: + if key == string: + return 'low' - # if nothing was detected, assume its low - return 'low' + # if nothing was detected, assume its low + return 'low' def get_baked_images(sets): - images = [] - for set in sets: - name_texture = "{}_".format(set.name) - for image in bpy.data.images: - if name_texture in image.name: - images.append(image) + images = [] + for set in sets: + name_texture = "{}_".format(set.name) + for image in bpy.data.images: + if name_texture in image.name: + images.append(image) - return images + return images def get_bake_sets(): - filtered = {} - for obj in bpy.context.selected_objects: - if obj.type == 'MESH': - filtered[obj] = get_object_type(obj) - - groups = [] - # Group by names - for obj in filtered: - name = get_set_name(obj) - - if len(groups)==0: - groups.append([obj]) - else: - isFound = False - for group in groups: - groupName = get_set_name(group[0]) - if name == groupName: - group.append(obj) - isFound = True - break - - if not isFound: - groups.append([obj]) - - # Sort groups alphabetically - keys = [get_set_name(group[0]) for group in groups] - keys.sort() - sorted_groups = [] - for key in keys: - for group in groups: - if key == get_set_name(group[0]): - sorted_groups.append(group) - break - - groups = sorted_groups - # print("Keys: "+", ".join(keys)) - - - bake_sets = [] - for group in groups: - low = [] - high = [] - cage = [] - float = [] - for obj in group: - if filtered[obj] == 'low': - low.append(obj) - elif filtered[obj] == 'high': - high.append(obj) - elif filtered[obj] == 'cage': - cage.append(obj) - elif filtered[obj] == 'float': - float.append(obj) - - - name = get_set_name(group[0]) - bake_sets.append(BakeSet(name, low, cage, high, float)) - - return bake_sets + filtered = {} + for obj in bpy.context.selected_objects: + if obj.type == 'MESH': + filtered[obj] = get_object_type(obj) + + groups = [] + # Group by names + for obj in filtered: + name = get_set_name(obj) + + if len(groups)==0: + groups.append([obj]) + else: + isFound = False + for group in groups: + groupName = get_set_name(group[0]) + if name == groupName: + group.append(obj) + isFound = True + break + + if not isFound: + groups.append([obj]) + + # Sort groups alphabetically + keys = [get_set_name(group[0]) for group in groups] + keys.sort() + sorted_groups = [] + for key in keys: + for group in groups: + if key == get_set_name(group[0]): + sorted_groups.append(group) + break + + groups = sorted_groups + # print("Keys: "+", ".join(keys)) + + + bake_sets = [] + for group in groups: + low = [] + high = [] + cage = [] + float = [] + for obj in group: + if filtered[obj] == 'low': + low.append(obj) + elif filtered[obj] == 'high': + high.append(obj) + elif filtered[obj] == 'cage': + cage.append(obj) + elif filtered[obj] == 'float': + float.append(obj) + + + name = get_set_name(group[0]) + bake_sets.append(BakeSet(name, low, cage, high, float)) + + return bake_sets class BakeSet: - objects_low = [] #low poly geometry - objects_cage = [] #Cage low poly geometry - objects_high = [] #High poly geometry - objects_float = [] #Floating geometry - name = "" + objects_low = [] #low poly geometry + objects_cage = [] #Cage low poly geometry + objects_high = [] #High poly geometry + objects_float = [] #Floating geometry + name = "" - has_issues = False + has_issues = False - def __init__(self, name, objects_low, objects_cage, objects_high, objects_float): - self.objects_low = objects_low - self.objects_cage = objects_cage - self.objects_high = objects_high - self.objects_float = objects_float - self.name = name + def __init__(self, name, objects_low, objects_cage, objects_high, objects_float): + self.objects_low = objects_low + self.objects_cage = objects_cage + self.objects_high = objects_high + self.objects_float = objects_float + self.name = name - # Needs low poly objects to bake onto - if len(objects_low) == 0: - self.has_issues = True + # Needs low poly objects to bake onto + if len(objects_low) == 0: + self.has_issues = True - # Check Cage Object count to low poly count - if len(objects_cage) > 0 and (len(objects_low) != len(objects_cage)): - self.has_issues = True + # Check Cage Object count to low poly count + if len(objects_cage) > 0 and (len(objects_low) != len(objects_cage)): + self.has_issues = True - # Check for UV maps - for obj in objects_low: - if len(obj.data.uv_layers) == 0: - self.has_issues = True - break + # Check for UV maps + for obj in objects_low: + if len(obj.data.uv_layers) == 0: + self.has_issues = True + break def setup_vertex_color_selection(obj): - bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.mode_set(mode='OBJECT') - bpy.ops.object.select_all(action='DESELECT') - obj.select_set( state = True, view_layer = None) - bpy.context.view_layer.objects.active = obj - + bpy.ops.object.select_all(action='DESELECT') + obj.select_set( state = True, view_layer = None) + bpy.context.view_layer.objects.active = obj + - bpy.ops.object.mode_set(mode='VERTEX_PAINT') + bpy.ops.object.mode_set(mode='VERTEX_PAINT') - bpy.context.tool_settings.vertex_paint.brush.color = (0, 0, 0) - bpy.context.object.data.use_paint_mask = False - bpy.ops.paint.vertex_color_set() + bpy.context.tool_settings.vertex_paint.brush.color = (0, 0, 0) + bpy.context.object.data.use_paint_mask = False + bpy.ops.paint.vertex_color_set() - bpy.context.tool_settings.vertex_paint.brush.color = (1, 1, 1) - bpy.context.object.data.use_paint_mask = True - bpy.ops.paint.vertex_color_set() + bpy.context.tool_settings.vertex_paint.brush.color = (1, 1, 1) + bpy.context.object.data.use_paint_mask = True + bpy.ops.paint.vertex_color_set() - bpy.context.object.data.use_paint_mask = False + bpy.context.object.data.use_paint_mask = False - # Back to object mode - bpy.ops.object.mode_set(mode='OBJECT') + # Back to object mode + bpy.ops.object.mode_set(mode='OBJECT') def setup_vertex_color_dirty(obj): - print("setup_vertex_color_dirty {}".format(obj.name)) + print("setup_vertex_color_dirty {}".format(obj.name)) - bpy.ops.object.select_all(action='DESELECT') - obj.select_set( state = True, view_layer = None) - bpy.context.view_layer.objects.active = obj - bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.object.select_all(action='DESELECT') + obj.select_set( state = True, view_layer = None) + bpy.context.view_layer.objects.active = obj + bpy.ops.object.mode_set(mode='EDIT') - # Fill white then, - bm = bmesh.from_edit_mesh(obj.data) - colorLayer = bm.loops.layers.color.verify() + # Fill white then, + bm = bmesh.from_edit_mesh(obj.data) + colorLayer = bm.loops.layers.color.verify() - color = utilities_color.safe_color( (1, 1, 1) ) + color = utilities_color.safe_color( (1, 1, 1) ) - for face in bm.faces: - for loop in face.loops: - loop[colorLayer] = color - obj.data.update() + for face in bm.faces: + for loop in face.loops: + loop[colorLayer] = color + obj.data.update() - # Back to object mode - bpy.ops.object.mode_set(mode='OBJECT') - bpy.ops.paint.vertex_color_dirt(dirt_angle=pi/2) - bpy.ops.paint.vertex_color_dirt() + # Back to object mode + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.paint.vertex_color_dirt(dirt_angle=pi/2) + bpy.ops.paint.vertex_color_dirt() def setup_vertex_color_id_material(obj): - bpy.ops.object.select_all(action='DESELECT') - obj.select_set( state = True, view_layer = None) - bpy.context.view_layer.objects.active = obj + bpy.ops.object.select_all(action='DESELECT') + obj.select_set( state = True, view_layer = None) + bpy.context.view_layer.objects.active = obj - bpy.ops.object.mode_set(mode='EDIT') - bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE') + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE') - bm = bmesh.from_edit_mesh(obj.data) - # colorLayer = bm.loops.layers.color.verify() + bm = bmesh.from_edit_mesh(obj.data) + # colorLayer = bm.loops.layers.color.verify() - for i in range(len(obj.material_slots)): - slot = obj.material_slots[i] - if slot.material: + for i in range(len(obj.material_slots)): + slot = obj.material_slots[i] + if slot.material: - # Select related faces - bpy.ops.object.mode_set(mode='EDIT') - bpy.ops.mesh.select_all(action='DESELECT') + # Select related faces + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.select_all(action='DESELECT') - bm = bmesh.from_edit_mesh(bpy.context.active_object.data) - for face in bm.faces: - if face.material_index == i: - face.select = True + bm = bmesh.from_edit_mesh(bpy.context.active_object.data) + for face in bm.faces: + if face.material_index == i: + face.select = True - color = utilities_color.get_color_id(i, len(obj.material_slots)) + color = utilities_color.get_color_id(i, len(obj.material_slots)) - bpy.ops.object.mode_set(mode='VERTEX_PAINT') - bpy.context.tool_settings.vertex_paint.brush.color = color - bpy.context.object.data.use_paint_mask = True - bpy.ops.paint.vertex_color_set() + bpy.ops.object.mode_set(mode='VERTEX_PAINT') + bpy.context.tool_settings.vertex_paint.brush.color = color + bpy.context.object.data.use_paint_mask = True + bpy.ops.paint.vertex_color_set() - obj.data.update() + obj.data.update() - # Back to object mode - bpy.ops.object.mode_set(mode='OBJECT') + # Back to object mode + bpy.ops.object.mode_set(mode='OBJECT') def setup_vertex_color_id_element(obj): - bpy.ops.object.select_all(action='DESELECT') - obj.select_set( state = True, view_layer = None) - bpy.context.view_layer.objects.active = obj - bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.object.select_all(action='DESELECT') + obj.select_set( state = True, view_layer = None) + bpy.context.view_layer.objects.active = obj + bpy.ops.object.mode_set(mode='EDIT') - bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE') + bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE') - bm = bmesh.from_edit_mesh(obj.data) - colorLayer = bm.loops.layers.color.verify() + bm = bmesh.from_edit_mesh(obj.data) + colorLayer = bm.loops.layers.color.verify() - # Collect elements - processed = set([]) - groups = [] - for face in bm.faces: + # Collect elements + processed = set([]) + groups = [] + for face in bm.faces: - if face not in processed: - bpy.ops.mesh.select_all(action='DESELECT') - face.select = True - bpy.ops.mesh.select_linked(delimit={'NORMAL'}) - linked = [face for face in bm.faces if face.select] + if face not in processed: + bpy.ops.mesh.select_all(action='DESELECT') + face.select = True + bpy.ops.mesh.select_linked(delimit={'NORMAL'}) + linked = [face for face in bm.faces if face.select] - for link in linked: - processed.add(link) - groups.append(linked) + for link in linked: + processed.add(link) + groups.append(linked) - # Color each group - for i in range(0,len(groups)): - color = utilities_color.get_color_id(i, len(groups)) - color = utilities_color.safe_color( color ) - for face in groups[i]: - for loop in face.loops: - loop[colorLayer] = color - obj.data.update() + # Color each group + for i in range(0,len(groups)): + color = utilities_color.get_color_id(i, len(groups)) + color = utilities_color.safe_color( color ) + for face in groups[i]: + for loop in face.loops: + loop[colorLayer] = color + obj.data.update() - # Back to object mode - bpy.ops.object.mode_set(mode='OBJECT') + # Back to object mode + bpy.ops.object.mode_set(mode='OBJECT') def get_image_material(image): - # Claer & Create new material - material = None - if image.name in bpy.data.materials: - # Incorrect existing material, delete first and create new for cycles - material = bpy.data.materials[image.name] - material.user_clear() - bpy.data.materials.remove(material) - material = bpy.data.materials.new(image.name) - else: - material = bpy.data.materials.new(image.name) - - - # Cyles Material - if bpy.context.scene.render.engine == 'CYCLES' or bpy.context.scene.render.engine == 'BLENDER_EEVEE': - material.use_nodes = True - - # Image Node - node_image = None - if "image" in material.node_tree.nodes: - node_image = material.node_tree.nodes["image"] - else: - node_image = material.node_tree.nodes.new("ShaderNodeTexImage") - node_image.name = "image" - node_image.select = True - node_image.image = image - material.node_tree.nodes.active = node_image - - #Base Diffuse BSDF - node_diffuse = material.node_tree.nodes['Principled BSDF'] - - - if "_normal_" in image.name: - # Add Normal Map Nodes - node_normal_map = None - if "normal_map" in material.node_tree.nodes: - node_normal_map = material.node_tree.nodes["normal_map"] - else: - node_normal_map = material.node_tree.nodes.new("ShaderNodeNormalMap") - node_normal_map.name = "normal_map" - - # Tangent or World space - if(image.name.endswith("normal_tangent")): - node_normal_map.space = 'TANGENT' - elif(image.name.endswith("normal_object")): - node_normal_map.space = 'WORLD' - - # image to normal_map link - material.node_tree.links.new(node_image.outputs[0], node_normal_map.inputs[1]) - - # normal_map to diffuse_bsdf link - material.node_tree.links.new(node_normal_map.outputs[0], node_diffuse.inputs[19]) - - node_normal_map.location = node_diffuse.location - Vector((200, 0)) - node_image.location = node_normal_map.location - Vector((200, 0)) - - else: - # Other images display as Color - # dump(node_image.color_mapping.bl_rna.property_tags) - - # image node to diffuse node link - material.node_tree.links.new(node_image.outputs[0], node_diffuse.inputs[0]) - - return material - - elif bpy.context.scene.render.engine == 'BLENDER_EEVEE': - material.use_nodes = True - - texture = None - if image.name in bpy.data.textures: - texture = bpy.data.textures[image.name] - else: - texture = bpy.data.textures.new(image.name, 'IMAGE') - - texture.image = image - slot = material.texture_slot.add() - slot.texture = texture - slot.mapping = 'FLAT' - - # return material + # Claer & Create new material + material = None + if image.name in bpy.data.materials: + # Incorrect existing material, delete first and create new for cycles + material = bpy.data.materials[image.name] + material.user_clear() + bpy.data.materials.remove(material) + material = bpy.data.materials.new(image.name) + else: + material = bpy.data.materials.new(image.name) + + + # Cyles Material + if bpy.context.scene.render.engine == 'CYCLES' or bpy.context.scene.render.engine == 'BLENDER_EEVEE': + material.use_nodes = True + + # Image Node + node_image = None + if "image" in material.node_tree.nodes: + node_image = material.node_tree.nodes["image"] + else: + node_image = material.node_tree.nodes.new("ShaderNodeTexImage") + node_image.name = "image" + node_image.select = True + node_image.image = image + material.node_tree.nodes.active = node_image + + #Base Diffuse BSDF + node_diffuse = material.node_tree.nodes['Principled BSDF'] + + + if "_normal_" in image.name: + # Add Normal Map Nodes + node_normal_map = None + if "normal_map" in material.node_tree.nodes: + node_normal_map = material.node_tree.nodes["normal_map"] + else: + node_normal_map = material.node_tree.nodes.new("ShaderNodeNormalMap") + node_normal_map.name = "normal_map" + + # Tangent or World space + if(image.name.endswith("normal_tangent")): + node_normal_map.space = 'TANGENT' + elif(image.name.endswith("normal_object")): + node_normal_map.space = 'WORLD' + + # image to normal_map link + material.node_tree.links.new(node_image.outputs[0], node_normal_map.inputs[1]) + + # normal_map to diffuse_bsdf link + material.node_tree.links.new(node_normal_map.outputs[0], node_diffuse.inputs[19]) + + node_normal_map.location = node_diffuse.location - Vector((200, 0)) + node_image.location = node_normal_map.location - Vector((200, 0)) + + else: + # Other images display as Color + # dump(node_image.color_mapping.bl_rna.property_tags) + + # image node to diffuse node link + material.node_tree.links.new(node_image.outputs[0], node_diffuse.inputs[0]) + + return material + + elif bpy.context.scene.render.engine == 'BLENDER_EEVEE': + material.use_nodes = True + + texture = None + if image.name in bpy.data.textures: + texture = bpy.data.textures[image.name] + else: + texture = bpy.data.textures.new(image.name, 'IMAGE') + + texture.image = image + slot = material.texture_slot.add() + slot.texture = texture + slot.mapping = 'FLAT' + + # return material diff --git a/utilities_color.py b/utilities_color.py index a3eb908..b2ce576 100644 --- a/utilities_color.py +++ b/utilities_color.py @@ -15,213 +15,213 @@ gamma = 2.2 def assign_slot(obj, index): - if index < len(obj.material_slots): - obj.material_slots[index].material = get_material(index) + if index < len(obj.material_slots): + obj.material_slots[index].material = get_material(index) - # Verify color - assign_color(index) + # Verify color + assign_color(index) def safe_color(color): - if len(color) == 3: - if bpy.app.version > (2, 80, 0): - # Newer blender versions use RGBA - return (color[0], color[1], color[2], 1) - else: - return color - elif len(color) == 4: - if bpy.app.version > (2, 80, 0): - # Newer blender versions use RGBA - return color - else: - return (color[0], color[1], color[2]) - - return color + if len(color) == 3: + if bpy.app.version > (2, 80, 0): + # Newer blender versions use RGBA + return (color[0], color[1], color[2], 1) + else: + return color + elif len(color) == 4: + if bpy.app.version > (2, 80, 0): + # Newer blender versions use RGBA + return color + else: + return (color[0], color[1], color[2]) + + return color def assign_color(index): - material = get_material(index) - if material: - # material.use_nodes = False - - rgb = get_color(index) - rgba = (rgb[0], rgb[1], rgb[2], 1) + material = get_material(index) + if material: + # material.use_nodes = False + + rgb = get_color(index) + rgba = (rgb[0], rgb[1], rgb[2], 1) - if material.use_nodes and bpy.context.scene.render.engine == 'CYCLES' or material.use_nodes and bpy.context.scene.render.engine == 'BLENDER_EEVEE' : - # Cycles material (Preferred for baking) - material.node_tree.nodes["Principled BSDF"].inputs[0].default_value = rgba - material.diffuse_color = rgba + if material.use_nodes and bpy.context.scene.render.engine == 'CYCLES' or material.use_nodes and bpy.context.scene.render.engine == 'BLENDER_EEVEE' : + # Cycles material (Preferred for baking) + material.node_tree.nodes["Principled BSDF"].inputs[0].default_value = rgba + material.diffuse_color = rgba - elif not material.use_nodes and bpy.context.scene.render.engine == 'BLENDER_EEVEE': - # Legacy render engine, not suited for baking - material.diffuse_color = rgba + elif not material.use_nodes and bpy.context.scene.render.engine == 'BLENDER_EEVEE': + # Legacy render engine, not suited for baking + material.diffuse_color = rgba def get_material(index): - name = get_name(index) + name = get_name(index) - # Material already exists? - if name in bpy.data.materials: - material = bpy.data.materials[name]; + # Material already exists? + if name in bpy.data.materials: + material = bpy.data.materials[name]; - # Check for incorrect matreials for current render engine - if not material: - replace_material(index) + # Check for incorrect matreials for current render engine + if not material: + replace_material(index) - if not material.use_nodes and bpy.context.scene.render.engine == 'CYCLES': - replace_material(index) + if not material.use_nodes and bpy.context.scene.render.engine == 'CYCLES': + replace_material(index) - elif material.use_nodes and bpy.context.scene.render.engine == 'BLENDER_EEVEE': - replace_material(index) + elif material.use_nodes and bpy.context.scene.render.engine == 'BLENDER_EEVEE': + replace_material(index) - else: - return material; + else: + return material; - print("Could nt find {} , create a new one??".format(name)) + print("Could nt find {} , create a new one??".format(name)) - material = create_material(index) - assign_color(index) - return material + material = create_material(index) + assign_color(index) + return material # Replaace an existing material with a new one # This is sometimes necessary after switching the render engine def replace_material(index): - name = get_name(index) + name = get_name(index) - print("Replace material and create new") + print("Replace material and create new") - # Check if material exists - if name in bpy.data.materials: - material = bpy.data.materials[name]; + # Check if material exists + if name in bpy.data.materials: + material = bpy.data.materials[name]; - # Collect material slots we have to re-assign - slots = [] - for obj in bpy.context.view_layer.objects: - for slot in obj.material_slots: - if slot.material == material: - slots.append(slot) + # Collect material slots we have to re-assign + slots = [] + for obj in bpy.context.view_layer.objects: + for slot in obj.material_slots: + if slot.material == material: + slots.append(slot) - # Get new material - material.user_clear() - bpy.data.materials.remove(material) - - # Re-assign new material to all previous slots - material = create_material(index) - for slot in slots: - slot.material = material; + # Get new material + material.user_clear() + bpy.data.materials.remove(material) + + # Re-assign new material to all previous slots + material = create_material(index) + for slot in slots: + slot.material = material; def create_material(index): - name = get_name(index) + name = get_name(index) - # Create new image instead - material = bpy.data.materials.new(name) - material.preview_render_type = 'FLAT' + # Create new image instead + material = bpy.data.materials.new(name) + material.preview_render_type = 'FLAT' - if bpy.context.scene.render.engine == 'CYCLES': - # Cycles: prefer nodes as it simplifies baking - material.use_nodes = True + if bpy.context.scene.render.engine == 'CYCLES': + # Cycles: prefer nodes as it simplifies baking + material.use_nodes = True - return material + return material def get_name(index): - return (material_prefix+"{:02d}").format(index) + return (material_prefix+"{:02d}").format(index) def get_color(index): - if index < bpy.context.scene.texToolsSettings.color_ID_count: - return getattr(bpy.context.scene.texToolsSettings, "color_ID_color_{}".format(index)) + if index < bpy.context.scene.texToolsSettings.color_ID_count: + return getattr(bpy.context.scene.texToolsSettings, "color_ID_color_{}".format(index)) - # Default return (Black) - return (0, 0, 0) + # Default return (Black) + return (0, 0, 0) def set_color(index, color): - if index < bpy.context.scene.texToolsSettings.color_ID_count: - setattr(bpy.context.scene.texToolsSettings, "color_ID_color_{}".format(index), color) + if index < bpy.context.scene.texToolsSettings.color_ID_count: + setattr(bpy.context.scene.texToolsSettings, "color_ID_color_{}".format(index), color) def validate_face_colors(obj): - # Validate face colors and material slots - previous_mode = bpy.context.object.mode; - count = bpy.context.scene.texToolsSettings.color_ID_count + # Validate face colors and material slots + previous_mode = bpy.context.object.mode; + count = bpy.context.scene.texToolsSettings.color_ID_count - # Verify enough material slots - if len(obj.material_slots) < count: - for i in range(count): - if len(obj.material_slots) < count: - bpy.ops.object.material_slot_add() - assign_slot(obj, len(obj.material_slots)-1) - else: - break + # Verify enough material slots + if len(obj.material_slots) < count: + for i in range(count): + if len(obj.material_slots) < count: + bpy.ops.object.material_slot_add() + assign_slot(obj, len(obj.material_slots)-1) + else: + break - # TODO: Check face.material_index - bpy.ops.object.mode_set(mode='EDIT') - bm = bmesh.from_edit_mesh(obj.data); - for face in bm.faces: - face.material_index%= count - obj.data.update() + # TODO: Check face.material_index + bpy.ops.object.mode_set(mode='EDIT') + bm = bmesh.from_edit_mesh(obj.data); + for face in bm.faces: + face.material_index%= count + obj.data.update() - # Remove material slots that are not used - if len(obj.material_slots) > count: - bpy.ops.object.mode_set(mode='OBJECT') - for i in range(len(obj.material_slots) - count): - if len(obj.material_slots) > count: - # Remove last - bpy.context.object.active_material_index = len(obj.material_slots)-1 - bpy.ops.object.material_slot_remove() + # Remove material slots that are not used + if len(obj.material_slots) > count: + bpy.ops.object.mode_set(mode='OBJECT') + for i in range(len(obj.material_slots) - count): + if len(obj.material_slots) > count: + # Remove last + bpy.context.object.active_material_index = len(obj.material_slots)-1 + bpy.ops.object.material_slot_remove() - # Restore previous mode - bpy.ops.object.mode_set(mode=previous_mode) + # Restore previous mode + bpy.ops.object.mode_set(mode=previous_mode) def hex_to_color(hex): - - hex = hex.strip('#') - lv = len(hex) - fin = list(int(hex[i:i + lv // 3], 16) for i in range(0, lv, lv // 3)) - r = pow(fin[0] / 255, gamma) - g = pow(fin[1] / 255, gamma) - b = pow(fin[2] / 255, gamma) - fin.clear() - fin.append(r) - fin.append(g) - fin.append(b) - return tuple(fin) + + hex = hex.strip('#') + lv = len(hex) + fin = list(int(hex[i:i + lv // 3], 16) for i in range(0, lv, lv // 3)) + r = pow(fin[0] / 255, gamma) + g = pow(fin[1] / 255, gamma) + b = pow(fin[2] / 255, gamma) + fin.clear() + fin.append(r) + fin.append(g) + fin.append(b) + return tuple(fin) def color_to_hex(color): - rgb = [] - for i in range(3): - rgb.append( pow(color[i] , 1.0/gamma) ) + rgb = [] + for i in range(3): + rgb.append( pow(color[i] , 1.0/gamma) ) - r = int(rgb[0]*255) - g = int(rgb[1]*255) - b = int(rgb[2]*255) + r = int(rgb[0]*255) + g = int(rgb[1]*255) + b = int(rgb[2]*255) - return "#{:02X}{:02X}{:02X}".format(r,g,b) + return "#{:02X}{:02X}{:02X}".format(r,g,b) def get_color_id(index, count): - # Get unique color - color = Color() - color.hsv = ( index / (count) ), 0.9, 1.0 - - return color \ No newline at end of file + # Get unique color + color = Color() + color.hsv = ( index / (count) ), 0.9, 1.0 + + return color \ No newline at end of file diff --git a/utilities_meshtex.py b/utilities_meshtex.py index 47d4760..810e812 100644 --- a/utilities_meshtex.py +++ b/utilities_meshtex.py @@ -7,114 +7,114 @@ from mathutils import Vector # Find a mesh that contains UV mesh shape keys def find_uv_mesh(objects, insideModifiers=True): - for obj in objects: - # Requires mesh & UV channel - if obj and obj.type == 'MESH': + for obj in objects: + # Requires mesh & UV channel + if obj and obj.type == 'MESH': - # Contains shape keys? - if obj.data.shape_keys and len(obj.data.shape_keys.key_blocks) == 2: - if "uv" in obj.data.shape_keys.key_blocks and "model" in obj.data.shape_keys.key_blocks: - return obj + # Contains shape keys? + if obj.data.shape_keys and len(obj.data.shape_keys.key_blocks) == 2: + if "uv" in obj.data.shape_keys.key_blocks and "model" in obj.data.shape_keys.key_blocks: + return obj - if insideModifiers: - # Find via mesh deform modifier - if len(obj.modifiers) > 0: - for modifier in obj.modifiers: - if modifier.type == 'SURFACE_DEFORM': - if modifier.target: - if modifier.target.data.shape_keys and len(modifier.target.data.shape_keys.key_blocks) == 2: - return modifier.target - return None + if insideModifiers: + # Find via mesh deform modifier + if len(obj.modifiers) > 0: + for modifier in obj.modifiers: + if modifier.type == 'SURFACE_DEFORM': + if modifier.target: + if modifier.target.data.shape_keys and len(modifier.target.data.shape_keys.key_blocks) == 2: + return modifier.target + return None # # Find meshes that can be wrapped aka texture meshes def find_texture_meshes(objects): - obj_textures = [] + obj_textures = [] - for obj in objects: - if obj and obj.type == 'MESH': - if find_uv_mesh([obj], insideModifiers=False) == None: - if obj not in obj_textures: - obj_textures.append(obj) + for obj in objects: + if obj and obj.type == 'MESH': + if find_uv_mesh([obj], insideModifiers=False) == None: + if obj not in obj_textures: + obj_textures.append(obj) - return obj_textures + return obj_textures def uv_mesh_clear(obj_uv): - # Remove Previous Modifiers - if "Solidify" in obj_uv.modifiers: - obj_uv.modifiers.remove( obj_uv.modifiers["Solidify"] ) - # Remove Solidify and push modifiers + # Remove Previous Modifiers + if "Solidify" in obj_uv.modifiers: + obj_uv.modifiers.remove( obj_uv.modifiers["Solidify"] ) + # Remove Solidify and push modifiers def uv_mesh_fit(obj_uv, obj_textures): - # Clear first shape transition - bpy.context.scene.texToolsSettings.meshtexture_wrap = 0 - - # Clear modifiers first - uv_mesh_clear(obj_uv) - - - # Add solidify modifier - modifier_solid = obj_uv.modifiers.new(name="Solidify", type='SOLIDIFY') - modifier_solid.offset = 1 - modifier_solid.thickness = 0 #scale*0.1 #10% height - modifier_solid.use_even_offset = True - modifier_solid.thickness_clamp = 0 - modifier_solid.use_quality_normals = True - - min_z = obj_uv.location.z - max_z = obj_uv.location.z - for i in range(len(obj_textures)): - obj = obj_textures[i] - - # Min Max Z - if i == 0: - min_z = get_bbox(obj)['min'].z - max_z = get_bbox(obj)['max'].z - else: - min_z = min(min_z, get_bbox(obj)['min'].z) - max_z = max(max_z, get_bbox(obj)['max'].z) - - # Set thickness - size = max(0.1, (max_z - min_z)) - min_z-= size*0.25 #Padding - max_z+= size*0.25 - size = (max_z - min_z) - - modifier_solid.thickness = size - - # Set offset - if size > 0: - p_z = (obj_uv.location.z - min_z) / (max_z - min_z) - modifier_solid.offset = -(p_z-0.5)/0.5 - else: - modifier_solid.offset = 0 + # Clear first shape transition + bpy.context.scene.texToolsSettings.meshtexture_wrap = 0 + + # Clear modifiers first + uv_mesh_clear(obj_uv) + + + # Add solidify modifier + modifier_solid = obj_uv.modifiers.new(name="Solidify", type='SOLIDIFY') + modifier_solid.offset = 1 + modifier_solid.thickness = 0 #scale*0.1 #10% height + modifier_solid.use_even_offset = True + modifier_solid.thickness_clamp = 0 + modifier_solid.use_quality_normals = True + + min_z = obj_uv.location.z + max_z = obj_uv.location.z + for i in range(len(obj_textures)): + obj = obj_textures[i] + + # Min Max Z + if i == 0: + min_z = get_bbox(obj)['min'].z + max_z = get_bbox(obj)['max'].z + else: + min_z = min(min_z, get_bbox(obj)['min'].z) + max_z = max(max_z, get_bbox(obj)['max'].z) + + # Set thickness + size = max(0.1, (max_z - min_z)) + min_z-= size*0.25 #Padding + max_z+= size*0.25 + size = (max_z - min_z) + + modifier_solid.thickness = size + + # Set offset + if size > 0: + p_z = (obj_uv.location.z - min_z) / (max_z - min_z) + modifier_solid.offset = -(p_z-0.5)/0.5 + else: + modifier_solid.offset = 0 def get_bbox(obj): - corners = [obj.matrix_world @ Vector(corner) for corner in obj.bound_box] - - # Get world space Min / Max - box_min = Vector((corners[0].x, corners[0].y, corners[0].z)) - box_max = Vector((corners[0].x, corners[0].y, corners[0].z)) - for corner in corners: - # box_min.x = -8 - box_min.x = min(box_min.x, corner.x) - box_min.y = min(box_min.y, corner.y) - box_min.z = min(box_min.z, corner.z) - - box_max.x = max(box_max.x, corner.x) - box_max.y = max(box_max.y, corner.y) - box_max.z = max(box_max.z, corner.z) - - return { - 'min':box_min, - 'max':box_max, - 'size':(box_max-box_min), - 'center':box_min+(box_max-box_min)/2 - } \ No newline at end of file + corners = [obj.matrix_world @ Vector(corner) for corner in obj.bound_box] + + # Get world space Min / Max + box_min = Vector((corners[0].x, corners[0].y, corners[0].z)) + box_max = Vector((corners[0].x, corners[0].y, corners[0].z)) + for corner in corners: + # box_min.x = -8 + box_min.x = min(box_min.x, corner.x) + box_min.y = min(box_min.y, corner.y) + box_min.z = min(box_min.z, corner.z) + + box_max.x = max(box_max.x, corner.x) + box_max.y = max(box_max.y, corner.y) + box_max.z = max(box_max.z, corner.z) + + return { + 'min':box_min, + 'max':box_max, + 'size':(box_max-box_min), + 'center':box_min+(box_max-box_min)/2 + } \ No newline at end of file diff --git a/utilities_texel.py b/utilities_texel.py index 42df878..c6f68d1 100644 --- a/utilities_texel.py +++ b/utilities_texel.py @@ -11,128 +11,128 @@ image_material_prefix = "TT_checker_" # Return all faces of selected objects or only selected faces def get_selected_object_faces(): - object_faces_indexies = {} + object_faces_indexies = {} - previous_mode = bpy.context.object.mode + previous_mode = bpy.context.object.mode - if bpy.context.object.mode == 'EDIT': - # Only selected Mesh faces - obj = bpy.context.active_object - if obj.type == 'MESH' and obj.data.uv_layers: - bm = bmesh.from_edit_mesh(obj.data) - bm.faces.ensure_lookup_table() - object_faces_indexies[obj] = [face.index for face in bm.faces if face.select] - else: - # Selected objects with all faces each - selected_objects = [obj for obj in bpy.context.selected_objects] - for obj in selected_objects: - if obj.type == 'MESH' and obj.data.uv_layers: - bpy.ops.object.mode_set(mode='OBJECT') - bpy.ops.object.select_all(action='DESELECT') - bpy.context.view_layer.objects.active = obj - obj.select_set( state = True, view_layer = None) + if bpy.context.object.mode == 'EDIT': + # Only selected Mesh faces + obj = bpy.context.active_object + if obj.type == 'MESH' and obj.data.uv_layers: + bm = bmesh.from_edit_mesh(obj.data) + bm.faces.ensure_lookup_table() + object_faces_indexies[obj] = [face.index for face in bm.faces if face.select] + else: + # Selected objects with all faces each + selected_objects = [obj for obj in bpy.context.selected_objects] + for obj in selected_objects: + if obj.type == 'MESH' and obj.data.uv_layers: + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.select_all(action='DESELECT') + bpy.context.view_layer.objects.active = obj + obj.select_set( state = True, view_layer = None) - bpy.ops.object.mode_set(mode='EDIT') - bm = bmesh.from_edit_mesh(obj.data) - bm.faces.ensure_lookup_table() - object_faces_indexies[obj] = [face.index for face in bm.faces] + bpy.ops.object.mode_set(mode='EDIT') + bm = bmesh.from_edit_mesh(obj.data) + bm.faces.ensure_lookup_table() + object_faces_indexies[obj] = [face.index for face in bm.faces] - bpy.ops.object.mode_set(mode=previous_mode) + bpy.ops.object.mode_set(mode=previous_mode) - return object_faces_indexies + return object_faces_indexies def get_object_texture_image(obj): - previous_mode = bpy.context.active_object.mode - bpy.ops.object.mode_set(mode='OBJECT') + previous_mode = bpy.context.active_object.mode + bpy.ops.object.mode_set(mode='OBJECT') - # Search in material & texture slots - for slot_mat in obj.material_slots: + # Search in material & texture slots + for slot_mat in obj.material_slots: - if slot_mat.material: + if slot_mat.material: - # Check for traditional texture slots in material - for slot_tex in slot_mat.material.texture_paint_slots: - if slot_tex and slot_tex.texture and hasattr(slot_tex.texture , 'image'): - return slot_tex.texture.image + # Check for traditional texture slots in material + for slot_tex in slot_mat.material.texture_paint_slots: + if slot_tex and slot_tex.texture and hasattr(slot_tex.texture , 'image'): + return slot_tex.texture.image - # Check if material uses Nodes - if hasattr(slot_mat.material , 'node_tree'): - if slot_mat.material.node_tree: - for node in slot_mat.material.node_tree.nodes: - if type(node) is bpy.types.ShaderNodeTexImage: - if node.image: - return node.image + # Check if material uses Nodes + if hasattr(slot_mat.material , 'node_tree'): + if slot_mat.material.node_tree: + for node in slot_mat.material.node_tree.nodes: + if type(node) is bpy.types.ShaderNodeTexImage: + if node.image: + return node.image - + - return None + return None def image_resize(image, size_x, size_y): - if image and image.source == 'FILE' or image.source == 'GENERATED': - image.generated_width = int(size_x) - image.generated_height = int(size_y) - image.scale( int(size_x), int(size_y) ) - - + if image and image.source == 'FILE' or image.source == 'GENERATED': + image.generated_width = int(size_x) + image.generated_height = int(size_y) + image.scale( int(size_x), int(size_y) ) + + def checker_images_cleanup(): - # Clean up unused images - for image in bpy.data.images: - if image and image_material_prefix in image.name: - # Remove unused images - if not image.users: - image.user_clear() - bpy.data.images.remove(image) - return - - # Check if name missmatches size - name = get_checker_name(image.generated_type , image.size[0], image.size[1]) - if image.name != name: - # In cycles find related material as well - if image.name in bpy.data.materials: - bpy.data.materials[image.name].name = name - image.name = name - - for material in bpy.data.materials: - if material and image_material_prefix in material.name: - # Remove unused images - if not material.users: - material.user_clear() - bpy.data.materials.remove(material) + # Clean up unused images + for image in bpy.data.images: + if image and image_material_prefix in image.name: + # Remove unused images + if not image.users: + image.user_clear() + bpy.data.images.remove(image) + return + + # Check if name missmatches size + name = get_checker_name(image.generated_type , image.size[0], image.size[1]) + if image.name != name: + # In cycles find related material as well + if image.name in bpy.data.materials: + bpy.data.materials[image.name].name = name + image.name = name + + for material in bpy.data.materials: + if material and image_material_prefix in material.name: + # Remove unused images + if not material.users: + material.user_clear() + bpy.data.materials.remove(material) def get_checker_name(mode, size_x, size_y): - return (image_material_prefix+"{1}x{2}_{0}").format(mode, size_x, size_y) + return (image_material_prefix+"{1}x{2}_{0}").format(mode, size_x, size_y) def get_area_triangle_uv(A,B,C, size_x, size_y): - scale_x = size_x / max(size_x, size_y) - scale_y = size_y / max(size_x, size_y) - A.x/=scale_x - B.x/=scale_x - C.x/=scale_x - - A.y/=scale_y - B.y/=scale_y - C.y/=scale_y + scale_x = size_x / max(size_x, size_y) + scale_y = size_y / max(size_x, size_y) + A.x/=scale_x + B.x/=scale_x + C.x/=scale_x + + A.y/=scale_y + B.y/=scale_y + C.y/=scale_y - return get_area_triangle(A,B,C) + return get_area_triangle(A,B,C) def get_area_triangle(A,B,C): - # Heron's formula: http://www.1728.org/triang.htm - # area = square root (s • (s - a) • (s - b) • (s - c)) - a = (B-A).length - b = (C-B).length - c = (A-C).length - s = (a+b+c)/2 - - # Use abs(s-a) for values that otherwise generate negative values e.g. pinched UV verts, otherwise math domain error - return math.sqrt(s * abs(s-a) * abs(s-b) * abs(s-c)) + # Heron's formula: http://www.1728.org/triang.htm + # area = square root (s • (s - a) • (s - b) • (s - c)) + a = (B-A).length + b = (C-B).length + c = (A-C).length + s = (a+b+c)/2 + + # Use abs(s-a) for values that otherwise generate negative values e.g. pinched UV verts, otherwise math domain error + return math.sqrt(s * abs(s-a) * abs(s-b) * abs(s-c)) diff --git a/utilities_ui.py b/utilities_ui.py index 690274b..3e4ad9c 100644 --- a/utilities_ui.py +++ b/utilities_ui.py @@ -11,160 +11,160 @@ from . import op_bake preview_collections = {} size_textures = [ - ('32', '32', ''), - ('64', '64', ''), - ('128', '128', ''), - ('256', '256', ''), - ('512', '512', ''), - ('1024', '1024', ''), - ('2048', '2048', ''), - ('4096', '4096', ''), - ('8192', '8192', '') - ] + ('32', '32', ''), + ('64', '64', ''), + ('128', '128', ''), + ('256', '256', ''), + ('512', '512', ''), + ('1024', '1024', ''), + ('2048', '2048', ''), + ('4096', '4096', ''), + ('8192', '8192', '') + ] preview_icons = bpy.utils.previews.new() def icon_get(name): - return preview_icons[name].icon_id + return preview_icons[name].icon_id def GetContextView3D(): - for window in bpy.context.window_manager.windows: - screen = window.screen - for area in screen.areas: - if area.type == 'VIEW_3D': - for region in area.regions: - if region.type == 'WINDOW': - override = {'window': window, 'screen': screen, 'area': area, 'region': region, 'scene': bpy.context.scene, 'edit_object': bpy.context.edit_object, 'active_object': bpy.context.active_object, 'selected_objects': bpy.context.selected_objects} # Stuff the override context with very common requests by operators. MORE COULD BE NEEDED! - return override - return None + for window in bpy.context.window_manager.windows: + screen = window.screen + for area in screen.areas: + if area.type == 'VIEW_3D': + for region in area.regions: + if region.type == 'WINDOW': + override = {'window': window, 'screen': screen, 'area': area, 'region': region, 'scene': bpy.context.scene, 'edit_object': bpy.context.edit_object, 'active_object': bpy.context.active_object, 'selected_objects': bpy.context.selected_objects} # Stuff the override context with very common requests by operators. MORE COULD BE NEEDED! + return override + return None def GetContextViewUV(): - for window in bpy.context.window_manager.windows: - screen = window.screen - for area in screen.areas: - if area.type == 'IMAGE_EDITOR': - for region in area.regions: - if region.type == 'WINDOW': - override = {'window': window, 'screen': screen, 'area': area, 'region': region, 'scene': bpy.context.scene, 'edit_object': bpy.context.edit_object, 'active_object': bpy.context.active_object, 'selected_objects': bpy.context.selected_objects} # Stuff the override context with very common requests by operators. MORE COULD BE NEEDED! - return override - return None + for window in bpy.context.window_manager.windows: + screen = window.screen + for area in screen.areas: + if area.type == 'IMAGE_EDITOR': + for region in area.regions: + if region.type == 'WINDOW': + override = {'window': window, 'screen': screen, 'area': area, 'region': region, 'scene': bpy.context.scene, 'edit_object': bpy.context.edit_object, 'active_object': bpy.context.active_object, 'selected_objects': bpy.context.selected_objects} # Stuff the override context with very common requests by operators. MORE COULD BE NEEDED! + return override + return None def icon_register(fileName): - name = fileName.split('.')[0] # Don't include file extension - icons_dir = os.path.join(os.path.dirname(__file__), "icons") - preview_icons.load(name, os.path.join(icons_dir, fileName), 'IMAGE') + name = fileName.split('.')[0] # Don't include file extension + icons_dir = os.path.join(os.path.dirname(__file__), "icons") + preview_icons.load(name, os.path.join(icons_dir, fileName), 'IMAGE') def get_padding(): - size_min = min(bpy.context.scene.texToolsSettings.size[0],bpy.context.scene.texToolsSettings.size[1]) - return bpy.context.scene.texToolsSettings.padding / size_min + size_min = min(bpy.context.scene.texToolsSettings.size[0],bpy.context.scene.texToolsSettings.size[1]) + return bpy.context.scene.texToolsSettings.padding / size_min def generate_bake_mode_previews(): - # We are accessing all of the information that we generated in the register function below - preview_collection = preview_collections["thumbnail_previews"] - image_location = preview_collection.images_location - VALID_EXTENSIONS = ('.png', '.jpg', '.jpeg') - - enum_items = [] - - # Generate the thumbnails - for i, image in enumerate(os.listdir(image_location)): - mode = image[0:-4] - print(".. .{}".format(mode)) - - - if image.endswith(VALID_EXTENSIONS) and mode in op_bake.modes: - filepath = os.path.join(image_location, image) - thumb = preview_collection.load(filepath, filepath, 'IMAGE') - enum_items.append((image, mode, "", thumb.icon_id, i)) - - return enum_items - + # We are accessing all of the information that we generated in the register function below + preview_collection = preview_collections["thumbnail_previews"] + image_location = preview_collection.images_location + VALID_EXTENSIONS = ('.png', '.jpg', '.jpeg') + + enum_items = [] + + # Generate the thumbnails + for i, image in enumerate(os.listdir(image_location)): + mode = image[0:-4] + print(".. .{}".format(mode)) + + + if image.endswith(VALID_EXTENSIONS) and mode in op_bake.modes: + filepath = os.path.join(image_location, image) + thumb = preview_collection.load(filepath, filepath, 'IMAGE') + enum_items.append((image, mode, "", thumb.icon_id, i)) + + return enum_items + def get_bake_mode(): - return str(bpy.context.scene.TT_bake_mode).replace(".png","").lower() + return str(bpy.context.scene.TT_bake_mode).replace(".png","").lower() class op_popup(bpy.types.Operator): - bl_idname = "ui.textools_popup" - bl_label = "Message" + bl_idname = "ui.textools_popup" + bl_label = "Message" - message : StringProperty() + message : StringProperty() - def execute(self, context): - self.report({'INFO'}, self.message) - print(self.message) - return {'FINISHED'} + def execute(self, context): + self.report({'INFO'}, self.message) + print(self.message) + return {'FINISHED'} - def invoke(self, context, event): - wm = context.window_manager - return wm.invoke_popup(self, width=200, height=200) + def invoke(self, context, event): + wm = context.window_manager + return wm.invoke_popup(self, width=200, height=200) - def draw(self, context): - self.layout.label(text=self.message) + def draw(self, context): + self.layout.label(text=self.message) def on_bakemode_set(self, context): - print("Set '{}'".format(bpy.context.scene.TT_bake_mode)) - utilities_bake.on_select_bake_mode(get_bake_mode()) + print("Set '{}'".format(bpy.context.scene.TT_bake_mode)) + utilities_bake.on_select_bake_mode(get_bake_mode()) def register(): - from bpy.types import Scene - from bpy.props import StringProperty, EnumProperty - - print("_______REgister previews") - - # Operators - # bpy.utils.register_class(op_popup) - - # global preview_icons - # preview_icons = bpy.utils.previews.new() - - # Create a new preview collection (only upon register) - preview_collection = bpy.utils.previews.new() - preview_collection.images_location = os.path.join(os.path.dirname(__file__), "resources/bake_modes") - preview_collections["thumbnail_previews"] = preview_collection - - - # This is an EnumProperty to hold all of the images - # You really can save it anywhere in bpy.types.* Just make sure the location makes sense - bpy.types.Scene.TT_bake_mode = EnumProperty( - items=generate_bake_mode_previews(), - update = on_bakemode_set, - default = 'normal_tangent.png' - ) - - + from bpy.types import Scene + from bpy.props import StringProperty, EnumProperty + + print("_______REgister previews") + + # Operators + # bpy.utils.register_class(op_popup) + + # global preview_icons + # preview_icons = bpy.utils.previews.new() + + # Create a new preview collection (only upon register) + preview_collection = bpy.utils.previews.new() + preview_collection.images_location = os.path.join(os.path.dirname(__file__), "resources/bake_modes") + preview_collections["thumbnail_previews"] = preview_collection + + + # This is an EnumProperty to hold all of the images + # You really can save it anywhere in bpy.types.* Just make sure the location makes sense + bpy.types.Scene.TT_bake_mode = EnumProperty( + items=generate_bake_mode_previews(), + update = on_bakemode_set, + default = 'normal_tangent.png' + ) + + def unregister(): - print("_______UNregister previews") + print("_______UNregister previews") - from bpy.types import WindowManager - for preview_collection in preview_collections.values(): - bpy.utils.previews.remove(preview_collection) - preview_collections.clear() - + from bpy.types import WindowManager + for preview_collection in preview_collections.values(): + bpy.utils.previews.remove(preview_collection) + preview_collections.clear() + - # Unregister icons - # global preview_icons - bpy.utils.previews.remove(preview_icons) + # Unregister icons + # global preview_icons + bpy.utils.previews.remove(preview_icons) - del bpy.types.Scene.TT_bake_mode + del bpy.types.Scene.TT_bake_mode if __name__ == "__main__": - register() + register() bpy.utils.register_class(op_popup) \ No newline at end of file diff --git a/utilities_uv.py b/utilities_uv.py index eb6bd87..0ba01b8 100644 --- a/utilities_uv.py +++ b/utilities_uv.py @@ -10,277 +10,277 @@ from . import settings from . import utilities_ui def selection_store(): - bm = bmesh.from_edit_mesh(bpy.context.active_object.data); - uv_layers = bm.loops.layers.uv.verify(); + bm = bmesh.from_edit_mesh(bpy.context.active_object.data); + uv_layers = bm.loops.layers.uv.verify(); - # https://blender.stackexchange.com/questions/5781/how-to-list-all-selected-elements-in-python - # print("selectionStore") - settings.selection_uv_mode = bpy.context.scene.tool_settings.uv_select_mode - settings.selection_uv_pivot = bpy.context.tool_settings.transform_pivot_point - - settings.selection_uv_pivot_pos = bpy.context.space_data.cursor_location.copy() + # https://blender.stackexchange.com/questions/5781/how-to-list-all-selected-elements-in-python + # print("selectionStore") + settings.selection_uv_mode = bpy.context.scene.tool_settings.uv_select_mode + settings.selection_uv_pivot = bpy.context.tool_settings.transform_pivot_point + + settings.selection_uv_pivot_pos = bpy.context.space_data.cursor_location.copy() - #VERT Selection - settings.selection_mode = tuple(bpy.context.scene.tool_settings.mesh_select_mode) - settings.selection_vert_indexies = [] - for vert in bm.verts: - if vert.select: - settings.selection_vert_indexies.append(vert.index) + #VERT Selection + settings.selection_mode = tuple(bpy.context.scene.tool_settings.mesh_select_mode) + settings.selection_vert_indexies = [] + for vert in bm.verts: + if vert.select: + settings.selection_vert_indexies.append(vert.index) - settings.selection_face_indexies = [] - for face in bm.faces: - if face.select: - settings.selection_face_indexies.append(face.index) + settings.selection_face_indexies = [] + for face in bm.faces: + if face.select: + settings.selection_face_indexies.append(face.index) - #Face selections (Loops) - settings.selection_uv_loops = [] - for face in bm.faces: - for loop in face.loops: - if loop[uv_layers].select: - settings.selection_uv_loops.append( [face.index, loop.vert.index] ) + #Face selections (Loops) + settings.selection_uv_loops = [] + for face in bm.faces: + for loop in face.loops: + if loop[uv_layers].select: + settings.selection_uv_loops.append( [face.index, loop.vert.index] ) def selection_restore(bm = None, uv_layers = None): - if bpy.context.object.mode != 'EDIT': - bpy.ops.object.mode_set(mode = 'EDIT') + if bpy.context.object.mode != 'EDIT': + bpy.ops.object.mode_set(mode = 'EDIT') - if not bm: - bm = bmesh.from_edit_mesh(bpy.context.active_object.data); - if not uv_layers: - uv_layers = bm.loops.layers.uv.verify(); + if not bm: + bm = bmesh.from_edit_mesh(bpy.context.active_object.data); + if not uv_layers: + uv_layers = bm.loops.layers.uv.verify(); - # print("selectionRestore") - bpy.context.scene.tool_settings.uv_select_mode = settings.selection_uv_mode - bpy.context.tool_settings.transform_pivot_point = settings.selection_uv_pivot + # print("selectionRestore") + bpy.context.scene.tool_settings.uv_select_mode = settings.selection_uv_mode + bpy.context.tool_settings.transform_pivot_point = settings.selection_uv_pivot - contextViewUV = utilities_ui.GetContextViewUV() - if contextViewUV: - bpy.ops.uv.cursor_set(contextViewUV, location=settings.selection_uv_pivot_pos) + contextViewUV = utilities_ui.GetContextViewUV() + if contextViewUV: + bpy.ops.uv.cursor_set(contextViewUV, location=settings.selection_uv_pivot_pos) - bpy.ops.mesh.select_all(action='DESELECT') + bpy.ops.mesh.select_all(action='DESELECT') - if hasattr(bm.verts, "ensure_lookup_table"): - bm.verts.ensure_lookup_table() - # bm.edges.ensure_lookup_table() - bm.faces.ensure_lookup_table() + if hasattr(bm.verts, "ensure_lookup_table"): + bm.verts.ensure_lookup_table() + # bm.edges.ensure_lookup_table() + bm.faces.ensure_lookup_table() - #FACE selection - bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE') - for index in settings.selection_face_indexies: - if index < len(bm.faces): - bm.faces[index].select = True + #FACE selection + bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE') + for index in settings.selection_face_indexies: + if index < len(bm.faces): + bm.faces[index].select = True - #VERT Selection - bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT') - for index in settings.selection_vert_indexies: - if index < len(bm.verts): - bm.verts[index].select = True + #VERT Selection + bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT') + for index in settings.selection_vert_indexies: + if index < len(bm.verts): + bm.verts[index].select = True - #Selection Mode - bpy.context.scene.tool_settings.mesh_select_mode = settings.selection_mode + #Selection Mode + bpy.context.scene.tool_settings.mesh_select_mode = settings.selection_mode - #UV Face-UV Selections (Loops) - bpy.ops.uv.select_all(contextViewUV, action='DESELECT') - for uv_set in settings.selection_uv_loops: - for loop in bm.faces[ uv_set[0] ].loops: - if loop.vert.index == uv_set[1]: - loop[uv_layers].select = True - break + #UV Face-UV Selections (Loops) + bpy.ops.uv.select_all(contextViewUV, action='DESELECT') + for uv_set in settings.selection_uv_loops: + for loop in bm.faces[ uv_set[0] ].loops: + if loop.vert.index == uv_set[1]: + loop[uv_layers].select = True + break - bpy.context.view_layer.update() + bpy.context.view_layer.update() def get_selected_faces(): - bm = bmesh.from_edit_mesh(bpy.context.active_object.data); - faces = []; - for face in bm.faces: - if face.select: - faces.append(face) + bm = bmesh.from_edit_mesh(bpy.context.active_object.data); + faces = []; + for face in bm.faces: + if face.select: + faces.append(face) - return faces + return faces def set_selected_faces(faces): - bm = bmesh.from_edit_mesh(bpy.context.active_object.data); - uv_layers = bm.loops.layers.uv.verify(); - for face in faces: - for loop in face.loops: - loop[uv_layers].select = True + bm = bmesh.from_edit_mesh(bpy.context.active_object.data); + uv_layers = bm.loops.layers.uv.verify(); + for face in faces: + for loop in face.loops: + loop[uv_layers].select = True def get_selected_uvs(bm, uv_layers): - """Returns selected mesh vertices of selected UV's""" - uvs = [] - for face in bm.faces: - if face.select: - for loop in face.loops: - if loop[uv_layers].select: - uvs.append( loop[uv_layers] ) - return uvs + """Returns selected mesh vertices of selected UV's""" + uvs = [] + for face in bm.faces: + if face.select: + for loop in face.loops: + if loop[uv_layers].select: + uvs.append( loop[uv_layers] ) + return uvs def get_selected_uv_verts(bm, uv_layers): - """Returns selected mesh vertices of selected UV's""" - verts = set() - for face in bm.faces: - if face.select: - for loop in face.loops: - if loop[uv_layers].select: - verts.add( loop.vert ) - return list(verts) + """Returns selected mesh vertices of selected UV's""" + verts = set() + for face in bm.faces: + if face.select: + for loop in face.loops: + if loop[uv_layers].select: + verts.add( loop.vert ) + return list(verts) def get_selected_uv_edges(bm, uv_layers): - """Returns selected mesh edges of selected UV's""" - verts = get_selected_uv_verts(bm, uv_layers) - edges = [] - for edge in bm.edges: - if edge.verts[0] in verts and edge.verts[1] in verts: - edges.append(edge) - return edges + """Returns selected mesh edges of selected UV's""" + verts = get_selected_uv_verts(bm, uv_layers) + edges = [] + for edge in bm.edges: + if edge.verts[0] in verts and edge.verts[1] in verts: + edges.append(edge) + return edges def get_selected_uv_faces(bm, uv_layers): - """Returns selected mesh faces of selected UV's""" - faces = [] - for face in bm.faces: - if face.select: - count = 0 - for loop in face.loops: - if loop[uv_layers].select: - count+=1 - if count == len(face.loops): - faces.append(face) - return faces + """Returns selected mesh faces of selected UV's""" + faces = [] + for face in bm.faces: + if face.select: + count = 0 + for loop in face.loops: + if loop[uv_layers].select: + count+=1 + if count == len(face.loops): + faces.append(face) + return faces def get_vert_to_uv(bm, uv_layers): - vert_to_uv = {} - for face in bm.faces: - for loop in face.loops: - vert = loop.vert - uv = loop[uv_layers] - if vert not in vert_to_uv: - vert_to_uv[vert] = [uv]; - else: - vert_to_uv[vert].append(uv) - return vert_to_uv + vert_to_uv = {} + for face in bm.faces: + for loop in face.loops: + vert = loop.vert + uv = loop[uv_layers] + if vert not in vert_to_uv: + vert_to_uv[vert] = [uv]; + else: + vert_to_uv[vert].append(uv) + return vert_to_uv def get_uv_to_vert(bm, uv_layers): - uv_to_vert = {} - for face in bm.faces: - for loop in face.loops: - vert = loop.vert - uv = loop[uv_layers] - if uv not in uv_to_vert: - uv_to_vert[ uv ] = vert; - return uv_to_vert + uv_to_vert = {} + for face in bm.faces: + for loop in face.loops: + vert = loop.vert + uv = loop[uv_layers] + if uv not in uv_to_vert: + uv_to_vert[ uv ] = vert; + return uv_to_vert def getSelectionBBox(): - bm = bmesh.from_edit_mesh(bpy.context.active_object.data); - uv_layers = bm.loops.layers.uv.verify(); - - bbox = {} - - boundsMin = Vector((99999999.0,99999999.0)) - boundsMax = Vector((-99999999.0,-99999999.0)) - boundsCenter = Vector((0.0,0.0)) - countFaces = 0; - - for face in bm.faces: - if face.select: - for loop in face.loops: - if loop[uv_layers].select is True: - uv = loop[uv_layers].uv - boundsMin.x = min(boundsMin.x, uv.x) - boundsMin.y = min(boundsMin.y, uv.y) - boundsMax.x = max(boundsMax.x, uv.x) - boundsMax.y = max(boundsMax.y, uv.y) - - boundsCenter+= uv - countFaces+=1 - - bbox['min'] = boundsMin - bbox['max'] = boundsMax - bbox['width'] = (boundsMax - boundsMin).x - bbox['height'] = (boundsMax - boundsMin).y - - if countFaces == 0: - bbox['center'] = boundsMin - else: - bbox['center'] = boundsCenter / countFaces - - bbox['area'] = bbox['width'] * bbox['height'] - bbox['minLength'] = min(bbox['width'], bbox['height']) - - return bbox; + bm = bmesh.from_edit_mesh(bpy.context.active_object.data); + uv_layers = bm.loops.layers.uv.verify(); + + bbox = {} + + boundsMin = Vector((99999999.0,99999999.0)) + boundsMax = Vector((-99999999.0,-99999999.0)) + boundsCenter = Vector((0.0,0.0)) + countFaces = 0; + + for face in bm.faces: + if face.select: + for loop in face.loops: + if loop[uv_layers].select is True: + uv = loop[uv_layers].uv + boundsMin.x = min(boundsMin.x, uv.x) + boundsMin.y = min(boundsMin.y, uv.y) + boundsMax.x = max(boundsMax.x, uv.x) + boundsMax.y = max(boundsMax.y, uv.y) + + boundsCenter+= uv + countFaces+=1 + + bbox['min'] = boundsMin + bbox['max'] = boundsMax + bbox['width'] = (boundsMax - boundsMin).x + bbox['height'] = (boundsMax - boundsMin).y + + if countFaces == 0: + bbox['center'] = boundsMin + else: + bbox['center'] = boundsCenter / countFaces + + bbox['area'] = bbox['width'] * bbox['height'] + bbox['minLength'] = min(bbox['width'], bbox['height']) + + return bbox; def getSelectionIslands(bm=None, uv_layers=None): - if bm == None: - bm = bmesh.from_edit_mesh(bpy.context.active_object.data) - uv_layers = bm.loops.layers.uv.verify() + if bm == None: + bm = bmesh.from_edit_mesh(bpy.context.active_object.data) + uv_layers = bm.loops.layers.uv.verify() - #Reference A: https://github.com/nutti/Magic-UV/issues/41 - #Reference B: https://github.com/c30ra/uv-align-distribute/blob/v2.2/make_island.py + #Reference A: https://github.com/nutti/Magic-UV/issues/41 + #Reference B: https://github.com/c30ra/uv-align-distribute/blob/v2.2/make_island.py - #Extend selection - if bpy.context.scene.tool_settings.use_uv_select_sync == False: - bpy.ops.uv.select_linked() + #Extend selection + if bpy.context.scene.tool_settings.use_uv_select_sync == False: + bpy.ops.uv.select_linked() - #Collect selected UV faces - faces_selected = []; - for face in bm.faces: - if face.select and face.loops[0][uv_layers].select: - faces_selected.append(face) - - #Collect UV islands - # faces_parsed = [] - faces_unparsed = faces_selected.copy() - islands = [] - - for face in faces_selected: - if face in faces_unparsed: - - #Select single face - bpy.ops.uv.select_all(action='DESELECT') - face.loops[0][uv_layers].select = True; - bpy.ops.uv.select_linked()#Extend selection - - #Collect faces - islandFaces = [face]; - for faceAll in faces_unparsed: - if faceAll != face and faceAll.select and faceAll.loops[0][uv_layers].select: - islandFaces.append(faceAll) - - for faceAll in islandFaces: - faces_unparsed.remove(faceAll) - - #Assign Faces to island - islands.append(islandFaces) - - #Restore selection - # for face in faces_selected: - # for loop in face.loops: - # loop[uv_layers].select = True - - - print("Islands: {}x".format(len(islands))) - return islands + #Collect selected UV faces + faces_selected = []; + for face in bm.faces: + if face.select and face.loops[0][uv_layers].select: + faces_selected.append(face) + + #Collect UV islands + # faces_parsed = [] + faces_unparsed = faces_selected.copy() + islands = [] + + for face in faces_selected: + if face in faces_unparsed: + + #Select single face + bpy.ops.uv.select_all(action='DESELECT') + face.loops[0][uv_layers].select = True; + bpy.ops.uv.select_linked()#Extend selection + + #Collect faces + islandFaces = [face]; + for faceAll in faces_unparsed: + if faceAll != face and faceAll.select and faceAll.loops[0][uv_layers].select: + islandFaces.append(faceAll) + + for faceAll in islandFaces: + faces_unparsed.remove(faceAll) + + #Assign Faces to island + islands.append(islandFaces) + + #Restore selection + # for face in faces_selected: + # for loop in face.loops: + # loop[uv_layers].select = True + + + print("Islands: {}x".format(len(islands))) + return islands