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/" } # 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) 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 # Import general modules. Important: must be placed here and not on top import bpy import os import math import string import bpy.utils.previews from bpy.types import Menu, Operator, Panel, UIList from bpy.props import ( 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" class UV_OT_op_debug(bpy.types.Operator): bl_idname = "uv.op_debug" bl_label = "Debug" bl_description = "Open console and enable dbug mode" @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'} class UV_OT_op_disable_uv_sync(bpy.types.Operator): bl_idname = "uv.op_disable_uv_sync" bl_label = "Disable Sync" bl_description = "Disable UV sync mode" @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'} 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'} 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" select_type : bpy.props.StringProperty(default='low') @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 bpy.ops.object.select_all(action='DESELECT') for obj in objects: obj.select_set( state = True, view_layer = None) 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; 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: # 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) 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) # 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) 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 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 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_exclude_others: bpy.props.BoolProperty( name="Exclude Others", description="Exclude deselected objects when rendering", default = False ) bake_merge_object: bpy.props.BoolProperty( name="Merge Objects", description="Merge selected objects when rendering, results in significantly lower bake times", 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")) 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")) 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() row = col.row(align=True) row.prop(context.scene.texToolsSettings, "bake_exclude_others", text="Only Selected") #row = col.row(align=True) #row.prop(context.scene.texToolsSettings, "bake_merge_object", text="Merge Selected") 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" 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') 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 ..." 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 ..." 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" @classmethod def poll(cls, context): return True 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 # 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'} 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) 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")) 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") box.operator(op_meshtex_pattern.op.bl_idname, text="Create Pattern", icon_value = icon_get("op_meshtex_pattern")) keymaps = [] def 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) 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" 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')) 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")); 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")) 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")) 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")) 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")) # 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")) 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 ) def register(): 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) from bpy.utils import unregister_class for cls in reversed(classes): unregister_class(cls) #Unregister Icons utilities_ui.icon_unregister() #Unregister Settings del bpy.types.Scene.texToolsSettings #handle the keymap for km, kmi in keymaps: km.keymap_items.remove(kmi) keymaps.clear() #GUI Utilities utilities_ui.unregister() 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()