From f60b85b477503eff70e6c753c57c124caea4ec67 Mon Sep 17 00:00:00 2001 From: drewcassidy Date: Tue, 24 Dec 2019 11:59:46 -0800 Subject: [PATCH] PEP8 compliance --- op_align.py | 49 ++--- op_bake.py | 238 +++++++++++----------- op_bake_explode.py | 87 ++++---- op_color_from_directions.py | 59 +++--- op_color_from_materials.py | 12 +- op_color_io_export.py | 19 +- op_color_select.py | 27 +-- op_edge_split_bevel.py | 133 ++++++------ op_island_align_edge.py | 43 ++-- op_island_align_sort.py | 87 ++++---- op_island_align_world.py | 93 ++++----- op_island_straighten_edge_loops.py | 109 +++++----- op_meshtex_trim.py | 26 +-- op_rectify.py | 311 ++++++++++++++--------------- op_texel_density_set.py | 60 +++--- op_texture_open.py | 17 +- op_texture_preview.py | 32 +-- op_unwrap_edge_peel.py | 16 +- op_uv_crop.py | 28 +-- op_uv_resize.py | 128 ++++++------ utilities_bake.py | 144 ++++++------- utilities_color.py | 57 ++---- utilities_ui.py | 85 ++++---- 23 files changed, 882 insertions(+), 978 deletions(-) diff --git a/op_align.py b/op_align.py index ed5ce8b..42455de 100644 --- a/op_align.py +++ b/op_align.py @@ -13,25 +13,26 @@ class op(bpy.types.Operator): bl_label = "Align" bl_description = "Align vertices, edges or shells" bl_options = {'REGISTER', 'UNDO'} - - direction : bpy.props.StringProperty(name="Direction", default="top") + + direction: bpy.props.StringProperty(name="Direction", default="top") @classmethod def poll(cls, context): if not bpy.context.active_object: return False - #Only in Edit mode + # Only in Edit mode if bpy.context.active_object.mode != 'EDIT': return False - #Only in UV editor mode + # Only in UV editor mode if bpy.context.area.type != 'IMAGE_EDITOR': return False - - #Requires UV map + + # Requires UV map if not bpy.context.object.data.uv_layers: - return False #self.report({'WARNING'}, "Object must have more than one UV map") + # self.report({'WARNING'}, "Object must have more than one UV map") + return False # Not in Synced mode if bpy.context.scene.tool_settings.use_uv_select_sync: @@ -39,27 +40,23 @@ class op(bpy.types.Operator): return True - def execute(self, context): - + align(context, self.direction) return {'FINISHED'} - - - def align(context, direction): - #Store selection + # 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 + # B-Mesh 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() if len(obj.data.uv_layers) == 0: print("There is no UV channel or UV data set") @@ -71,12 +68,12 @@ def align(context, direction): mode = bpy.context.scene.tool_settings.uv_select_mode if mode == 'FACE' or mode == 'ISLAND': print("____ Align Islands") - - #Collect UV 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() @@ -84,13 +81,13 @@ def align(context, direction): # print("Island "+str(len(island))+"x faces, delta: "+str(delta.y)) if direction == "bottom": - delta = boundsAll['min'] - bounds['min'] + 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'] + delta = boundsAll['min'] - bounds['min'] bpy.ops.transform.translate(value=(delta.x, 0, 0)) elif direction == "right": delta = boundsAll['max'] - bounds['max'] @@ -98,7 +95,6 @@ def align(context, direction): else: print("Unkown direction: "+str(direction)) - elif mode == 'EDGE' or mode == 'VERTEX': print("____ Align Verts") @@ -117,15 +113,10 @@ def align(context, direction): elif direction == "right": luv.uv[0] = boundsAll['max'].x - bmesh.update_edit_mesh(obj.data) - #Restore selection + # Restore selection utilities_uv.selection_restore() -bpy.utils.register_class(op) - - - - +bpy.utils.register_class(op) diff --git a/op_bake.py b/op_bake.py index 8f08f77..a9a6d9d 100644 --- a/op_bake.py +++ b/op_bake.py @@ -8,19 +8,19 @@ from random import random from . import utilities_ui from . import settings -from . import utilities_bake as ub #Use shorthand ub = utitlites_bake +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={ +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' ), + '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), + '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'), @@ -30,12 +30,14 @@ modes={ 'wireframe': ub.BakeMode('bake_wireframe', type='EMIT', color=(0, 0, 0, 1), params=["bake_wireframe_size"]) } -if hasattr(bpy.types,"ShaderNodeBevel"): +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"]) - + 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): @@ -53,43 +55,45 @@ class op(bpy.types.Operator): 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() )) ) + 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 + 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 + 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) + 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)) - bpy.context.scene.render.engine = modes[mode].engine #Switch render engine + # Switch render engine + bpy.context.scene.render.engine = modes[mode].engine # Disable edit mode if bpy.context.view_layer.objects.active != None and bpy.context.object.mode != 'OBJECT': @@ -103,29 +107,33 @@ def bake(self, mode, size, bake_single, sampling_scale, samples, ray_distance): render_width = sampling_scale * size[0] render_height = sampling_scale * size[1] - for s in range(0,len(sets)): + 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 + # In Single mode bake into same texture + name_texture = "{}_{}".format(sets[0].name, mode) 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) ) + 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)) + 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)) + 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 @@ -136,7 +144,6 @@ def bake(self, mode, size, bake_single, sampling_scale, samples, ray_distance): 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 @@ -153,14 +160,13 @@ def bake(self, mode, size, bake_single, sampling_scale, samples, ray_distance): 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) + 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)) @@ -171,13 +177,14 @@ def bake(self, mode, size, bake_single, sampling_scale, samples, ray_distance): # 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] + 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) + obj_low.select_set(state=True, view_layer=None) bpy.context.view_layer.objects.active = obj_low if modes[mode].engine == 'BLENDER_EEVEE': @@ -190,34 +197,33 @@ def bake(self, mode, size, bake_single, sampling_scale, samples, ray_distance): 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) + obj_high.select_set(state=True, view_layer=None) cycles_bake( - mode, + mode, bpy.context.scene.texToolsSettings.padding, - sampling_scale, - samples, + sampling_scale, + samples, ray_distance, - len(set.objects_high) > 0, - obj_cage + 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) + obj_high.select_set(state=True, view_layer=None) + obj_low.select_set(state=True, view_layer=None) cycles_bake( - mode, + mode, 0, - sampling_scale, - samples, - ray_distance, + sampling_scale, + samples, + ray_distance, len(set.objects_float) > 0, obj_cage ) @@ -231,16 +237,16 @@ def bake(self, mode, size, bake_single, sampling_scale, samples, ray_distance): 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]) - + 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) + apply_composite( + image, modes[mode].composite, bpy.context.scene.texToolsSettings.bake_curvature_size) # image.save() @@ -248,8 +254,6 @@ def bake(self, mode, size, bake_single, sampling_scale, samples, ray_distance): ub.restore_materials() - - def apply_composite(image, scene_name, size): previous_scene = bpy.context.window.scene @@ -258,31 +262,33 @@ def apply_composite(image, scene_name, size): 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) + 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 - #Setup composite nodes for Curvature + # 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)) + print("Assign offset: {}".format( + scene.node_tree.nodes["Offset"].outputs[0].default_value)) # 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) - #Copy pixels + # Copy pixels image.pixels = image_viewer_node.pixels[:] image.update() @@ -291,14 +297,13 @@ def apply_composite(image, scene_name, size): if image_render_result: bpy.data.images.remove(image_render_result) - #Restore scene & remove other 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 @@ -307,7 +312,7 @@ def get_last_item(key_name, 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]] @@ -315,8 +320,6 @@ def get_last_item(key_name, collection): return None - - def setup_image(mode, name, width, height, path, is_clear): image = None @@ -331,7 +334,6 @@ def setup_image(mode, name, width, height, path, is_clear): # bpy.data.images[name].update() # if bpy.data.images[name].has_data == False: - # Previous image does not have data, remove first # print("Image pointer exists but no data "+name) @@ -343,13 +345,13 @@ def setup_image(mode, name, width, height, path, is_clear): 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) + 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' + image.colorspace_settings.name = 'Non-Color' else: image.colorspace_settings.name = 'sRGB' - else: # Reuse existing Image image = bpy.data.images[name] @@ -364,7 +366,6 @@ def setup_image(mode, name, width, height, path, is_clear): image.generated_color = modes[mode].color image.generated_type = 'BLANK' - image.file_format = 'TARGA' # TODO: Verify that the path exists @@ -373,11 +374,10 @@ def setup_image(mode, name, width, height, path, is_clear): 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!!!") + print("ERROR, need spare material to setup active image texture to bake!!!") else: for slot in obj.material_slots: if slot.material: @@ -397,46 +397,45 @@ def setup_image_bake_node(obj, image): tree.nodes.active = node - def assign_vertex_color(mode, obj): if modes[mode].setVColor: modes[mode].setVColor(obj) - def assign_material(mode, obj, material_bake=None, material_empty=None): ub.store_materials(obj) bpy.context.view_layer.objects.active = obj - obj.select_set( state = True, view_layer = None) + 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); + 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 + 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"].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"].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"].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: @@ -450,11 +449,11 @@ def assign_material(mode, obj, material_bake=None, material_empty=None): bpy.ops.object.material_slot_assign() elif material_empty: - #Assign material_empty if no material available + # 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: + 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() @@ -466,40 +465,33 @@ def assign_material(mode, obj, material_bake=None, material_empty=None): bpy.ops.object.mode_set(mode='OBJECT') - - - - - def get_material(mode): - - if modes[mode].material == "": - return None # No material setup requires + 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\\" + 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\\" - + 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) + bpy.ops.wm.append(filename=name, directory=path, + link=False, autoselect=False) 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': + # if modes[mode].engine == 'BLENDER_EEVEE': # # Snippet: https://gist.github.com/AndrewRayCode/760c4634a77551827de41ed67585064b # bpy.context.scene.render.bake_margin = padding @@ -518,11 +510,10 @@ def cycles_bake(mode, padding, sampling_scale, samples, ray_distance, is_multi, # bpy.ops.object.bake_image() - - if modes[mode].engine == 'CYCLES' or modes[mode].engine == 'BLENDER_EEVEE' : + 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 + # 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' @@ -556,26 +547,37 @@ def cycles_bake(mode, padding, sampling_scale, samples, ray_distance, is_multi, if obj_cage is None: # Bake with Cage bpy.ops.object.bake( - type=modes[mode].type, - use_clear=False, - cage_extrusion=ray_distance, + type=modes[mode].type, + use_clear=False, + cage_extrusion=ray_distance, - use_selected_to_active=is_multi, + 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, + type=modes[mode].type, + use_clear=False, + cage_extrusion=ray_distance, - use_selected_to_active=is_multi, + use_selected_to_active=is_multi, normal_space=modes[mode].normal_space, - #Use Cage and assign object - use_cage=True, + # Use Cage and assign object + use_cage=True, cage_object=obj_cage.name ) + +bpy.utils.register_class(op) + 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 0a99679..be7a4a6 100644 --- a/op_bake_explode.py +++ b/op_bake_explode.py @@ -23,15 +23,12 @@ class op(bpy.types.Operator): return True - def execute(self, context): explode(self) return {'FINISHED'} - - def explode(self): sets = settings.sets @@ -40,13 +37,14 @@ def explode(self): 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_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] @@ -54,26 +52,23 @@ def explode(self): # 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())) + # max_bbox(list(set_bounds.values())) + bbox_max = set_bounds[sorted_sets[0]] # Offset sets into their direction dir_offset_last_bbox = {} - for i in range(0,6): - dir_offset_last_bbox[i] = bbox_max #bbox_all - + 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 - # 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 ) - - + offset_set(set, delta, avg_side*0.35, dir_offset_last_bbox) def offset_set(set, delta, margin, dir_offset_last_bbox): @@ -82,16 +77,16 @@ def offset_set(set, delta, margin, dir_offset_last_bbox): # Which Direction? delta_max = max(abs(delta.x), abs(delta.y), abs(delta.z)) - direction = [0,0,0] + direction = [0, 0, 0] if delta_max > 0: - for i in range(0,3): + 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] + direction = [0, 0, 1] delta = Vector((direction[0], direction[1], direction[2])) @@ -101,26 +96,26 @@ def offset_set(set, delta, margin, dir_offset_last_bbox): # Calculate Offset bbox = get_bbox_set(set) bbox_last = dir_offset_last_bbox[key] - - offset = Vector((0,0,0)) + + offset = Vector((0, 0, 0)) if delta.x == 1: - offset = delta * ( bbox_last['max'].x - bbox['min'].x ) + offset = delta * (bbox_last['max'].x - bbox['min'].x) elif delta.x == -1: - offset = delta * -( bbox_last['min'].x - bbox['max'].x ) - + offset = delta * -(bbox_last['min'].x - bbox['max'].x) + elif delta.y == 1: - offset = delta * ( bbox_last['max'].y - bbox['min'].y ) + offset = delta * (bbox_last['max'].y - bbox['min'].y) elif delta.y == -1: - offset = delta * -( bbox_last['min'].y - bbox['max'].y ) - + offset = delta * -(bbox_last['min'].y - bbox['max'].y) + elif delta.z == 1: - offset = delta * ( bbox_last['max'].z - bbox['min'].z ) + offset = delta * (bbox_last['max'].z - bbox['min'].z) elif delta.z == -1: - offset = delta * -( bbox_last['min'].z - bbox['max'].z ) + offset = delta * -(bbox_last['min'].z - bbox['max'].z) # Add margin - offset+= delta * margin + offset += delta * margin # Offset items # https://blenderartists.org/forum/showthread.php?237761-Blender-2-6-Set-keyframes-using-Python-script @@ -144,8 +139,6 @@ def offset_set(set, delta, margin, dir_offset_last_bbox): 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: @@ -162,39 +155,36 @@ def get_delta_key(delta): 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 + '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) ) + 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] @@ -206,16 +196,17 @@ def get_bbox(obj): 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 + '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 + +bpy.utils.register_class(op) diff --git a/op_color_from_directions.py b/op_color_from_directions.py index 520cef1..780d14d 100644 --- a/op_color_from_directions.py +++ b/op_color_from_directions.py @@ -7,25 +7,25 @@ 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' - ) + + 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") @@ -44,43 +44,39 @@ class op(bpy.types.Operator): if bpy.context.active_object.type != 'MESH': return False - #Only in UV editor mode + # 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); + # Collect groups + bm = bmesh.from_edit_mesh(bpy.context.active_object.data) face_directions = { - 'top':[], - 'bottom':[], - 'left':[], - 'right':[], - 'front':[], - 'back':[] + '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 @@ -114,7 +110,8 @@ def color_elements(self, context): 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']) + 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']) @@ -136,8 +133,8 @@ def color_elements(self, context): 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 = bmesh.from_edit_mesh(bpy.context.active_object.data) + if hasattr(bm.faces, "ensure_lookup_table"): bm.faces.ensure_lookup_table() # Select group @@ -148,7 +145,8 @@ def color_elements(self, context): # Assign to selection bpy.ops.uv.textools_color_assign(index=index_color) - index_color = (index_color+1) % bpy.context.scene.texToolsSettings.color_ID_count + 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) @@ -203,4 +201,5 @@ def color_elements(self, context): utilities_color.validate_face_colors(obj) ''' -bpy.utils.register_class(op) \ No newline at end of file + +bpy.utils.register_class(op) diff --git a/op_color_from_materials.py b/op_color_from_materials.py index e91d601..e145948 100644 --- a/op_color_from_materials.py +++ b/op_color_from_materials.py @@ -7,12 +7,12 @@ 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'} - @classmethod def poll(cls, context): @@ -28,21 +28,20 @@ class op(bpy.types.Operator): if bpy.context.active_object.type != 'MESH': return False - #Only in UV editor mode + # 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'} - 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: @@ -50,4 +49,5 @@ def color_materials(self, context): utilities_color.validate_face_colors(obj) -bpy.utils.register_class(op) \ No newline at end of file + +bpy.utils.register_class(op) diff --git a/op_color_io_export.py b/op_color_io_export.py index f3d9457..9f6a3b7 100644 --- a/op_color_io_export.py +++ b/op_color_io_export.py @@ -15,28 +15,29 @@ class op(bpy.types.Operator): @classmethod def poll(cls, context): - - #Only in UV editor mode + + # 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'} - 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) ) + 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.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 +bpy.utils.register_class(op) diff --git a/op_color_select.py b/op_color_select.py index 3b1b89d..03a5562 100644 --- a/op_color_select.py +++ b/op_color_select.py @@ -7,13 +7,14 @@ 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) + + index: bpy.props.IntProperty(description="Color Index", default=0) @classmethod def poll(cls, context): @@ -30,40 +31,42 @@ class op(bpy.types.Operator): if bpy.context.active_object.type != 'MESH': return False - #Only in UV editor mode + # 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'} - def select_color(self, context, index): - print("Color select "+str(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) ) + 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 + 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); + 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 + +bpy.utils.register_class(op) diff --git a/op_edge_split_bevel.py b/op_edge_split_bevel.py index 423fa4d..f8a686f 100644 --- a/op_edge_split_bevel.py +++ b/op_edge_split_bevel.py @@ -6,71 +6,64 @@ import operator from mathutils import Vector from collections import defaultdict -from itertools import chain # 'flattens' collection of iterables +from itertools import chain # 'flattens' collection of iterables 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'} - 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 - #Only in Edit mode + # Only in Edit mode if bpy.context.active_object.mode != 'EDIT': return False - #Requires UV map + # 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 - #Only in UV editor mode + # Only in UV editor mode if bpy.context.area.type != 'IMAGE_EDITOR': return False return True - def execute(self, context): main(self, self.radius) return {'FINISHED'} - def main(self, radius): - #Store selection + # Store selection utilities_uv.selection_store() 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() - 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) @@ -84,19 +77,16 @@ def main(self, radius): # print("Hard edge: {} - {}".format(edge.verts[0].index, edge.verts[1].index)) edges.append(edge) - # Get vert rails to slide + # 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) - 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 = [] @@ -104,77 +94,79 @@ def main(self, radius): 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} ) + 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} ) - + 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) + 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 - - - #Restore selection + # 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 ) + verts_edges.add(v) for v in B_links: - verts_edges.add( v ) + verts_edges.add(v) 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 @@ -207,12 +199,12 @@ def slide_uvs(vert, edge, face, edges, vert_rails, vert_to_uv): 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])) + 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] + verts = [A, B] if vert == A: verts.extend(B_links) elif vert == B: @@ -223,15 +215,14 @@ def slide_uvs(vert, edge, face, edges, vert_rails, vert_to_uv): print(" Verts: {}x = {}".format(len(verts), [v.index for v in verts])) print(" Rails:") - - delta = Vector((0,0)) + 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])) - + print(" #{} rails = {}".format(v.index, [ + ("{} - {}".format(e.verts[0].index, e.verts[1].index)) for e in rails])) for e in rails: # determine order @@ -248,7 +239,7 @@ def slide_uvs(vert, edge, face, edges, vert_rails, vert_to_uv): delta += (uv1-uv0).normalized() count += 1.0 - delta/=count + delta /= count if IS_DEBUG(): print("\r") @@ -261,7 +252,6 @@ def slide_uvs(vert, edge, face, edges, vert_rails, vert_to_uv): # 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)) @@ -287,12 +277,6 @@ 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]] @@ -310,20 +294,20 @@ def slide_face_uvs(uv_layers, edge, vert, face, radius, vert_to_uv): ''' - - def get_edge_prev_next(edge, edges): A = edge.verts[0] B = edge.verts[1] # print(" get_edge_prev_next {}x edges".format(len(edges))) - # v0_extends = [] + # 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] + + 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 @@ -342,7 +326,6 @@ def get_edge_face_pairs(edges): return edge_faces - def get_vert_edge_rails(edges): vert_rails = {} @@ -359,9 +342,9 @@ def get_vert_edge_rails(edges): 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 ] = [] + vert_rails[v0] = [] if v1 not in vert_rails: - vert_rails[ v1 ] = [] + vert_rails[v1] = [] if v0 in e.verts and e not in vert_rails[v0]: vert_rails[v0].append(e) diff --git a/op_island_align_edge.py b/op_island_align_edge.py index 90d9ef9..8780eb2 100644 --- a/op_island_align_edge.py +++ b/op_island_align_edge.py @@ -8,15 +8,16 @@ 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 + # Only in UV editor mode if bpy.context.area.type != 'IMAGE_EDITOR': return False @@ -26,51 +27,49 @@ class op(bpy.types.Operator): if bpy.context.active_object.type != 'MESH': return False - #Only in Edit mode + # 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 - #Requires UV map + # 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 + return False return True - def execute(self, context): - #Store selection + # Store selection utilities_uv.selection_store() main(context) - #Restore selection + # Restore selection utilities_uv.selection_restore() 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 = []; + + 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 @@ -91,11 +90,11 @@ def main(context): 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]; + 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)) @@ -103,14 +102,15 @@ def main(context): for f in faces_island: faces_unparsed.remove(f) - #Assign Faces to island + # 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]) + align_island(face_uvs[face][0].uv, face_uvs[face] + [1].uv, faces_islands[face]) def align_island(uv_vert0, uv_vert1, faces): @@ -126,7 +126,7 @@ def align_island(uv_vert0, uv_vert1, faces): loop[uv_layers].select = True diff = uv_vert1 - uv_vert0 - angle = math.atan2(diff.y, diff.x)%(math.pi/2) + angle = math.atan2(diff.y, diff.x) % (math.pi/2) bpy.ops.uv.select_linked() @@ -136,7 +136,8 @@ def align_island(uv_vert0, uv_vert1, faces): 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 70100f9..71afe58 100644 --- a/op_island_align_sort.py +++ b/op_island_align_sort.py @@ -18,9 +18,10 @@ class op(bpy.types.Operator): 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): @@ -31,25 +32,25 @@ class op(bpy.types.Operator): if bpy.context.active_object.type != 'MESH': return False - #Only in Edit mode + # Only in Edit mode if bpy.context.active_object.mode != 'EDIT': return False - #Only in UV editor mode + # Only in UV editor mode if bpy.context.area.type != 'IMAGE_EDITOR': return False - - #Requires UV map + + # Requires UV map if not bpy.context.object.data.uv_layers: - return False #self.report({'WARNING'}, "Object must have more than one UV map") + # self.report({'WARNING'}, "Object must have more than one UV map") + return False - #Not in Synced mode + # Not in Synced mode if bpy.context.scene.tool_settings.use_uv_select_sync: return False return True - def execute(self, context): main(context, self.is_vertical, self.padding) return {'FINISHED'} @@ -57,49 +58,48 @@ class op(bpy.types.Operator): def main(context, isVertical, padding): print("Executing IslandsAlignSort main {}".format(padding)) - - #Store selection + + # Store selection utilities_uv.selection_store() if bpy.context.tool_settings.transform_pivot_point != 'CURSOR': bpy.context.tool_settings.transform_pivot_point = 'CURSOR' - #Only in Face or Island mode + # 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() - islands = utilities_uv.getSelectionIslands() - allSizes = {} #https://stackoverflow.com/questions/613183/sort-a-python-dictionary-by-value + allSizes = {} # https://stackoverflow.com/questions/613183/sort-a-python-dictionary-by-value allBounds = {} print("Islands: "+str(len(islands))+"x") bpy.context.window_manager.progress_begin(0, len(islands)) - #Rotate to minimal bounds + # 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; + 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_end() - - #Position by sorted size in row - sortedSizes = sorted(allSizes.items(), key=operator.itemgetter(1))#Sort by values, store tuples + # Position by sorted size in row + # Sort by values, store tuples + sortedSizes = sorted(allSizes.items(), key=operator.itemgetter(1)) sortedSizes.reverse() offset = 0.0 for sortedSize in sortedSizes: @@ -107,23 +107,24 @@ def main(context, isVertical, padding): island = islands[index] bounds = allBounds[index] - #Select Island + # Select Island bpy.ops.uv.select_all(action='DESELECT') utilities_uv.set_selected_faces(island) - - #Offset Island + + # Offset Island if(isVertical): - delta = Vector((boundsAll['min'].x - bounds['min'].x, boundsAll['max'].y - bounds['max'].y)); + 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)); + 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 + # Restore selection utilities_uv.selection_restore() @@ -133,13 +134,14 @@ def alignIslandMinimalBounds(uv_layers, faces): utilities_uv.set_selected_faces(faces) steps = 8 - angle = 45; # Starting Angle, half each step + 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') + bpy.ops.transform.rotate( + value=(angle * math.pi / 180), orient_axis='Z') bbox = utilities_uv.getSelectionBBox() if i == 0: @@ -147,25 +149,28 @@ def alignIslandMinimalBounds(uv_layers, faces): 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; - + bpy.ops.transform.rotate( + value=(-angle * math.pi / 180), orient_axis='Z') + break if bbox['minLength'] < bboxPrevious['minLength']: - bboxPrevious = bbox; # Success + bboxPrevious = bbox # Success else: # Rotate Left - bpy.ops.transform.rotate(value=(-angle*2 * math.pi / 180), orient_axis='Z') + 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 + bboxPrevious = bbox # Success else: # Restore angle of this iteration - bpy.ops.transform.rotate(value=(angle * math.pi / 180), orient_axis='Z') + 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 + +bpy.utils.register_class(op) diff --git a/op_island_align_world.py b/op_island_align_world.py index 8333602..92f131e 100644 --- a/op_island_align_world.py +++ b/op_island_align_world.py @@ -6,13 +6,11 @@ import operator from mathutils import Vector from collections import defaultdict -from itertools import chain # 'flattens' collection of iterables +from itertools import chain # 'flattens' collection of iterables from . import utilities_uv - - class op(bpy.types.Operator): bl_idname = "uv.textools_island_align_world" bl_label = "Align World" @@ -34,57 +32,53 @@ class op(bpy.types.Operator): if not bpy.context.active_object: return False - #Only in Edit mode + # Only in Edit mode if bpy.context.active_object.mode != 'EDIT': return False - #Requires UV map + # 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 - #Only in UV editor mode + # Only in UV editor mode if bpy.context.area.type != 'IMAGE_EDITOR': return False return True - def execute(self, context): main(self) return {'FINISHED'} - def main(context): print("\n________________________\nis_global") - #Store selection + # Store selection utilities_uv.selection_store() bm = bmesh.from_edit_mesh(bpy.context.active_object.data) uv_layers = bm.loops.layers.uv.verify() - #Only in Face or Island mode + # 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)) + avg_normal = Vector((0, 0, 0)) for face in faces: - avg_normal+=face.normal - avg_normal/=len(faces) + avg_normal += face.normal + avg_normal /= len(faces) # avg_normal = (obj.matrix_world*avg_normal).normalized() @@ -93,44 +87,43 @@ def main(context): 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) + 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) + 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) - - print("align island: faces {}x n:{}, max:{}".format(len(faces), avg_normal, max_size)) + 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)) - #Restore selection + # 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_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] )) - + 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) @@ -142,33 +135,31 @@ def align_island(obj, bm, uv_layers, faces, x=0, y=1, flip_x=False, flip_y=False for edge in face.edges: if edge not in processed_edges: processed_edges.append(edge) - delta = edge.verts[0].co -edge.verts[1].co + 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): + 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] + 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, @@ -177,31 +168,24 @@ def align_island(obj, bm, uv_layers, faces, x=0, y=1, flip_x=False, flip_y=False 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)) + 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 += a_delta - avg_angle/=len(edges) # - math.pi/2 + 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') @@ -288,4 +272,5 @@ def align_island(obj, bm, uv_layers, faces, x=0, y=1, flip_x=False, flip_y=False # return ''' + bpy.utils.register_class(op) diff --git a/op_island_straighten_edge_loops.py b/op_island_straighten_edge_loops.py index 8dbf170..b70ae36 100644 --- a/op_island_straighten_edge_loops.py +++ b/op_island_straighten_edge_loops.py @@ -8,12 +8,13 @@ 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): @@ -23,54 +24,47 @@ class op(bpy.types.Operator): if bpy.context.active_object.type != 'MESH': return False - #Only in Edit mode + # Only in Edit mode if bpy.context.active_object.mode != 'EDIT': return False - #Only in UV editor mode + # Only in UV editor mode if bpy.context.area.type != 'IMAGE_EDITOR': return False - #Requires UV map + # 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 - + return False return True - def execute(self, context): - + main(context) return {'FINISHED'} - - def main(context): print("____________________________") - - #Store selection + + # 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 ] - + faces = [f for island in islands for f in island] # Get island faces - # utilities_uv.selection_restore(bm, uv_layers) - groups = get_edge_groups(bm, uv_layers, faces, edges, uvs) bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE') @@ -78,15 +72,10 @@ def main(context): for face in faces: face.select = True - print("Edges {}x".format(len(edges))) print("Groups {}x".format(len(groups))) # Restore 3D face selection - - - - # Restore UV seams and clear pins bpy.ops.uv.seams_from_islands() @@ -94,21 +83,16 @@ def main(context): edge_sets = [] for edges in groups: - edge_sets.append( EdgeSet(bm, uv_layers, edges, faces) ) + 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) for edge_set in sorted_sets: edge_set.straighten() - - #Restore selection + + # Restore selection utilities_uv.selection_restore() - class EdgeSet: @@ -136,11 +120,11 @@ class EdgeSet: 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] - + self.length += self.edge_length[e] def straighten(self): - print("Straight {}x at {:.2f} length ".format(len(self.edges), self.length)) + print("Straight {}x at {:.2f} length ".format( + len(self.edges), self.length)) # Get edge angles in UV space angles = {} @@ -148,17 +132,18 @@ class EdgeSet: 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) + 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] + 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)) - 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') @@ -167,11 +152,12 @@ class EdgeSet: 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) + 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) + 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) @@ -179,11 +165,13 @@ class EdgeSet: 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] - + 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] )) + print("Step, proc {} exp: {}".format( + [e.index for e in processed], [e.index for e in edges_expand])) if len(edges_expand) == 0: continue @@ -192,25 +180,31 @@ class EdgeSet: # 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])) + + 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] + 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() + 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] + 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("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] ] )) + 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 @@ -221,16 +215,10 @@ class EdgeSet: bpy.ops.uv.pin(clear=True) - - - - - - def get_edge_groups(bm, uv_layers, faces, edges, uvs): 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() @@ -253,7 +241,8 @@ def get_edge_groups(bm, uv_layers, faces, edges, uvs): 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] @@ -262,11 +251,7 @@ def get_edge_groups(bm, uv_layers, faces, edges, uvs): # unmatched.remove(e) # group.append(edge) - - return groups bpy.utils.register_class(op) - - diff --git a/op_meshtex_trim.py b/op_meshtex_trim.py index 1da1120..4fe08a7 100644 --- a/op_meshtex_trim.py +++ b/op_meshtex_trim.py @@ -9,8 +9,6 @@ import math from . import utilities_meshtex - - class op(bpy.types.Operator): bl_idname = "uv.textools_meshtex_trim" bl_label = "Trim" @@ -21,12 +19,12 @@ class op(bpy.types.Operator): 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: + if len(utilities_meshtex.find_texture_meshes(bpy.context.selected_objects)) > 0: return True return False @@ -36,42 +34,40 @@ class op(bpy.types.Operator): return {'FINISHED'} - def trim(self): - # Wrap the mesh texture around the + # 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" ) + self.report({'ERROR_INVALID_INPUT'}, "No UV mesh found") return # Collect texture meshes - obj_textures = utilities_meshtex.find_texture_meshes( bpy.context.selected_objects ) - + 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" ) + self.report({'ERROR_INVALID_INPUT'}, + "No meshes found for mesh textures") return # Setup Thickness utilities_meshtex.uv_mesh_fit(obj_uv, obj_textures) - # Apply bool modifier to trim for obj in obj_textures: name = "Trim UV" if name in obj.modifiers: - obj.modifiers.remove( obj.modifiers[name] ) + obj.modifiers.remove(obj.modifiers[name]) 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_rectify.py b/op_rectify.py index 600fdc0..b674e19 100644 --- a/op_rectify.py +++ b/op_rectify.py @@ -15,7 +15,7 @@ class op(bpy.types.Operator): 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: @@ -32,34 +32,31 @@ class op(bpy.types.Operator): return False return True - def execute(self, context): rectify(self, context) return {'FINISHED'} - precision = 3 def rectify(self, context): obj = bpy.context.active_object - bm = bmesh.from_edit_mesh(obj.data) uv_layers = bm.loops.layers.uv.verify() - #Store selection + # Store selection utilities_uv.selection_store() main(False) - #Restore selection + # Restore selection utilities_uv.selection_restore() -def main(square = False, snapToClosest = False): +def main(square=False, snapToClosest=False): startTime = time.clock() obj = bpy.context.active_object @@ -68,69 +65,72 @@ def main(square = False, snapToClosest = False): uv_layers = bm.loops.layers.uv.verify() # bm.faces.layers.tex.verify() # currently blender needs both layers. - face_act = bm.faces.active + face_act = bm.faces.active targetFace = face_act - - #if len(bm.faces) > allowedFaces: + + # if len(bm.faces) > allowedFaces: # operator.report({'ERROR'}, "selected more than " +str(allowedFaces) +" allowed faces.") - # return + # return + + edgeVerts, filteredVerts, selFaces, nonQuadFaces, vertsDict, noEdge = ListsOfVerts( + uv_layers, bm) - edgeVerts, filteredVerts, selFaces, nonQuadFaces, vertsDict, noEdge = ListsOfVerts(uv_layers, bm) - - if len(filteredVerts) is 0: return - if len(filteredVerts) is 1: + if len(filteredVerts) is 0: + return + if len(filteredVerts) is 1: SnapCursorToClosestSelected(filteredVerts) - return - + 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) + + MakeEqualDistanceBetweenVertsInLine( + filteredVerts, vertsDict, cursorClosestTo) return SuccessFinished(me, startTime) - - - #else: - - #active face checks + + # 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: + if l[uv_layers].select is False: targetFace = selFaces[0] - break - + 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 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 + # 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) + return SuccessFinished(me, startTime) def ListsOfVerts(uv_layers, bm): @@ -139,50 +139,52 @@ def ListsOfVerts(uv_layers, bm): filteredVerts = [] selFaces = [] nonQuadFaces = [] - vertsDict = defaultdict(list) #dict - + vertsDict = defaultdict(list) # dict + for f in bm.faces: isFaceSel = True facesEdgeVerts = [] if (f.select == False): continue - - #collect edge verts if any + + # 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 - + else: + isFaceSel = False + allEdgeVerts.extend(facesEdgeVerts) - if isFaceSel: + if isFaceSel: if len(f.verts) is not 4: nonQuadFaces.append(f) edgeVerts.extend(facesEdgeVerts) - else: + 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) - + + 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 + else: + filteredVerts = edgeVerts + return edgeVerts, filteredVerts, selFaces, nonQuadFaces, vertsDict, noEdge def ListQuasiContainsVect(list, vect): @@ -192,28 +194,23 @@ def ListQuasiContainsVect(list, vect): return False - def SnapCursorToClosestSelected(filteredVerts): - #TODO: snap to closest selected - if len(filteredVerts) is 1: + # TODO: snap to closest selected + if len(filteredVerts) is 1: SetAll2dCursorsTo(filteredVerts[0].uv.x, filteredVerts[0].uv.y) - - return - - + 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) + 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): @@ -227,191 +224,189 @@ def AreVectsLinedOnAxis(verts): areLinedX = False if abs(valY - v.uv.y) > allowedError: areLinedY = False - return areLinedX or areLinedY + return areLinedX or areLinedY +def ScaleTo0OnAxisAndCursor(filteredVerts, vertsDict, startv=None, horizontal=None): -def ScaleTo0OnAxisAndCursor(filteredVerts, vertsDict, startv = None, horizontal = None): - verts = filteredVerts - verts.sort(key=lambda x: x.uv[0]) #sort by .x - + 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): + 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: + if (slope > 1) or (slope < -1): + horizontal = False + else: horizontal = False - + if horizontal is True: if startv is None: - startv = first - + startv = first + SetAll2dCursorsTo(startv.uv.x, startv.uv.y) - #scale to 0 on 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 + 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 + startv = first SetAll2dCursorsTo(startv.uv.x, startv.uv.y) - #scale to 0 on X + # scale to 0 on X ScaleTo0('X') return - -def SetAll2dCursorsTo(x,y): +def SetAll2dCursorsTo(x, y): last_area = bpy.context.area.type bpy.context.area.type = 'IMAGE_EDITOR' - + bpy.ops.uv.cursor_set(location=(x, y)) bpy.context.area.type = last_area return - -def CursorClosestTo(verts, allowedError = 0.025): +def CursorClosestTo(verts, allowedError=0.025): ratioX, ratioY = ImageRatio() - - #any length that is certantly not smaller than distance of the closest + + # 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 + 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) + 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: + + if min is not 1000: return minV return None - - def SuccessFinished(me, startTime): - #use for backtrack of steps - #bpy.ops.ed.undo_push() + # 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: + + 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: + 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): +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 - + + 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): + 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 + 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)]: + 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)]: + 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)]: + 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)]: + 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'): +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) @@ -514,7 +509,8 @@ def FollowActiveUV(operator, me, f_act, faces, EXTEND_MODE = 'LENGTH_AVERAGE'): 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] + 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 @@ -541,11 +537,12 @@ def FollowActiveUV(operator, me, f_act, faces, EXTEND_MODE = 'LENGTH_AVERAGE'): if EXTEND_MODE == 'LENGTH_AVERAGE': bm.edges.index_update() - edge_lengths = [None] * len(bm.edges) #NoneType times the length of edges list - + # NoneType times the length of edges list + edge_lengths = [None] * len(bm.edges) + for f in faces: # we know its a quad - l_quad = f.loops[:] + l_quad = f.loops[:] l_pair_a = (l_quad[0], l_quad[2]) l_pair_b = (l_quad[1], l_quad[3]) @@ -564,7 +561,8 @@ def FollowActiveUV(operator, me, f_act, faces, EXTEND_MODE = 'LENGTH_AVERAGE'): edge_length_accum += e.calc_length() edge_length_total += 1 - edge_length_store[0] = edge_length_accum / edge_length_total + edge_length_store[0] = edge_length_accum / \ + edge_length_total # done with average length # ------------------------ @@ -577,7 +575,7 @@ def FollowActiveUV(operator, me, f_act, faces, EXTEND_MODE = 'LENGTH_AVERAGE'): def ImageRatio(): - ratioX, ratioY = 256,256 + ratioX, ratioY = 256, 256 for a in bpy.context.screen.areas: if a.type == 'IMAGE_EDITOR': img = a.spaces[0].image @@ -587,19 +585,18 @@ def ImageRatio(): return ratioX, ratioY - def Corners(corners): firstHighest = corners[0] for c in corners: if c.uv.y > firstHighest.uv.y: - firstHighest = c + 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 @@ -607,31 +604,29 @@ def Corners(corners): 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 - + 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: +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 - def hypotVert(v1, v2): hyp = hypot(v1.x - v2.x, v1.y - v2.y) return hyp -bpy.utils.register_class(op) \ No newline at end of file + +bpy.utils.register_class(op) diff --git a/op_texel_density_set.py b/op_texel_density_set.py index 6c6b107..31c6ab3 100644 --- a/op_texel_density_set.py +++ b/op_texel_density_set.py @@ -9,43 +9,43 @@ from collections import defaultdict 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 + # Only in UV editor mode if bpy.context.area.type != 'IMAGE_EDITOR': return False - #Requires UV map + # 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 False return True - def execute(self, context): set_texel_density( - self, + self, context, bpy.context.scene.texToolsSettings.texel_mode_scale, bpy.context.scene.texToolsSettings.texel_density @@ -53,7 +53,6 @@ class op(bpy.types.Operator): return {'FINISHED'} - def set_texel_density(self, context, mode, density): print("Set texel density!") @@ -61,10 +60,9 @@ def set_texel_density(self, context, mode, density): 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" ) + self.report({'ERROR_INVALID_INPUT'}, "No valid meshes or UV maps") return # Collect Images / textures @@ -76,15 +74,15 @@ def set_texel_density(self, context, mode, density): # Warning: No valid images if len(object_images) == 0: - self.report({'ERROR_INVALID_INPUT'}, "No Texture found. Assign Checker map or texture." ) + 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) + obj.select_set(state=True, view_layer=None) # Find image of object image = object_images[obj] @@ -118,35 +116,37 @@ def set_texel_density(self, context, mode, density): 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_uv = [loop[uv_layers].uv for loop in face.loops] + triangle_vt = [obj.matrix_world @ + vert.co for vert in face.verts] - #Triangle Areas + # Triangle Areas face_area_vt = utilities_texel.get_area_triangle( - triangle_vt[0], - triangle_vt[1], - triangle_vt[2] + 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[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]) + + 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)) + 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) @@ -166,7 +166,8 @@ def set_texel_density(self, context, mode, density): loop[uv_layers].select = True print("Scale: {} {}x".format(scale, len(group))) - bpy.ops.transform.resize(value=(scale, scale, 1), use_proportional_edit=False) + bpy.ops.transform.resize( + value=(scale, scale, 1), use_proportional_edit=False) # Restore selection utilities_uv.selection_restore() @@ -175,7 +176,7 @@ def set_texel_density(self, context, mode, density): 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) + obj.select_set(state=True, view_layer=None) bpy.context.view_layer.objects.active = list(object_faces.keys())[0] # Restore edit mode @@ -186,4 +187,5 @@ def set_texel_density(self, context, mode, density): 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 d73c261..be4a139 100644 --- a/op_texture_open.py +++ b/op_texture_open.py @@ -2,7 +2,9 @@ import bpy import bmesh import operator import math -import os, sys, subprocess +import os +import sys +import subprocess from . import settings from . import utilities_bake @@ -13,27 +15,26 @@ class op(bpy.types.Operator): bl_label = "Open Texture" bl_description = "Open the texture on the system" - name : bpy.props.StringProperty( + name: bpy.props.StringProperty( name="image name", - default = "" + default="" ) @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/ @@ -43,8 +44,8 @@ def open_texture(self, context): if sys.platform == "win32": os.startfile(path) else: - opener ="open" if sys.platform == "darwin" else "xdg-open" + opener = "open" if sys.platform == "darwin" else "xdg-open" subprocess.call([opener, path]) -bpy.utils.register_class(op) \ No newline at end of file +bpy.utils.register_class(op) diff --git a/op_texture_preview.py b/op_texture_preview.py index 7be3ac7..f7d5b15 100644 --- a/op_texture_preview.py +++ b/op_texture_preview.py @@ -12,12 +12,12 @@ from . import utilities_bake 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): @@ -26,25 +26,25 @@ class op(bpy.types.Operator): 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] + 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 @@ -57,9 +57,8 @@ def preview_texture(self, context): # bpy.ops.view3d.localview({'area': view_area}) # return - # Get background image - image = None + image = None for area in bpy.context.screen.areas: if area.type == 'IMAGE_EDITOR': image = area.spaces[0].image @@ -71,25 +70,25 @@ def preview_texture(self, context): bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.select_all(action='DESELECT') - obj.select_set( state = True, view_layer = None) + 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 + # 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' - + 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) + obj.select_set(state=True, view_layer=None) - if view_area: - #Change View mode to TEXTURED + if view_area: + # Change View mode to TEXTURED for space in view_area.spaces: if space.type == 'VIEW_3D': space.shading.type = 'MATERIAL' @@ -98,4 +97,5 @@ def preview_texture(self, context): # 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 + +bpy.utils.register_class(op) diff --git a/op_unwrap_edge_peel.py b/op_unwrap_edge_peel.py index 93d92d9..61ab79b 100644 --- a/op_unwrap_edge_peel.py +++ b/op_unwrap_edge_peel.py @@ -8,12 +8,13 @@ from math import pi 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): @@ -23,7 +24,7 @@ class op(bpy.types.Operator): if bpy.context.active_object.type != 'MESH': return False - #Only in Edit mode + # Only in Edit mode if bpy.context.active_object.mode != 'EDIT': return False @@ -43,10 +44,10 @@ 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.") + 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] @@ -58,7 +59,7 @@ def unwrap_edges_pipe(self, context): 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" ) + self.report({'ERROR_INVALID_INPUT'}, "No edges selected in the view") return # Convert linked selection to single UV island @@ -68,7 +69,7 @@ def unwrap_edges_pipe(self, context): selected_faces = [face for face in bm.faces if face.select] if len(selected_faces) == 0: - self.report({'ERROR_INVALID_INPUT'}, "No faces available" ) + self.report({'ERROR_INVALID_INPUT'}, "No faces available") return # Mark previous selected edges as Seam @@ -92,4 +93,5 @@ def unwrap_edges_pipe(self, context): # 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 + +bpy.utils.register_class(op) diff --git a/op_uv_crop.py b/op_uv_crop.py index 91f47e4..25ee6b7 100644 --- a/op_uv_crop.py +++ b/op_uv_crop.py @@ -8,59 +8,61 @@ from math import pi 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 - #Only in Edit mode + # Only in Edit mode if bpy.context.active_object.mode != 'EDIT': return False - #Only in UV editor mode + # Only in UV editor mode if bpy.context.area.type != 'IMAGE_EDITOR': return False - #Requires UV map + # Requires UV map if not bpy.context.object.data.uv_layers: return False 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() - # 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() - delta_position = Vector((padding/2,1-padding/2)) - Vector((bbox['min'].x, bbox['min'].y + bbox['height'])) + 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 + +bpy.utils.register_class(op) diff --git a/op_uv_resize.py b/op_uv_resize.py index b4461e2..c731b80 100644 --- a/op_uv_resize.py +++ b/op_uv_resize.py @@ -18,11 +18,11 @@ utilities_ui.icon_register("op_extend_canvas_BL_active.png") 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() + def on_dropdown_size_y(self, context): self.size_y = int(self.dropdown_size_y) # context.area.tag_redraw() @@ -34,63 +34,66 @@ class op(bpy.types.Operator): bl_description = "Resize or extend the UV area" bl_options = {'REGISTER', 'UNDO'} - size_x : bpy.props.IntProperty( - name = "Width", + size_x: bpy.props.IntProperty( + name="Width", description="padding size in pixels", - default = 1024, - min = 1, - max = 8192 + default=1024, + min=1, + max=8192 ) - size_y : bpy.props.IntProperty( - name = "Height", + size_y: bpy.props.IntProperty( + name="Height", description="padding size in pixels", - default = 1024, - min = 1, - max = 8192 + 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_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' + 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) + 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 + # Only in Edit mode if bpy.context.active_object.mode != 'EDIT': return False - #Only in UV editor mode + # Only in UV editor mode if bpy.context.area.type != 'IMAGE_EDITOR': return False - #Requires UV map + # 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] @@ -105,8 +108,7 @@ class op(bpy.types.Operator): self.dropdown_size_y = item[0] break - - return context.window_manager.invoke_props_dialog(self, width = 140) + return context.window_manager.invoke_props_dialog(self, width=140) def check(self, context): return True @@ -115,15 +117,14 @@ class op(bpy.types.Operator): # 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.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="") @@ -133,10 +134,11 @@ class op(bpy.types.Operator): col = layout.column(align=True) col.label(text="Direction") row = col.row(align=True) - row.prop(self,'direction', expand=True) + row.prop(self, 'direction', expand=True) # Summary - size_A = "{} x {}".format(bpy.context.scene.texToolsSettings.size[0], bpy.context.scene.texToolsSettings.size[1]) + 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) @@ -147,21 +149,19 @@ class op(bpy.types.Operator): size_A, size_B )) - layout.separator() - def execute(self, context): - #Store selection + # Store selection utilities_uv.selection_store() # Get start and end size - size_A = Vector([ + size_A = Vector([ bpy.context.scene.texToolsSettings.size[0], bpy.context.scene.texToolsSettings.size[1] ]) - size_B = Vector([ + size_B = Vector([ self.size_x, self.size_y ]) @@ -170,7 +170,7 @@ class op(bpy.types.Operator): self, context, self.direction, - size_A, + size_A, size_B ) resize_image( @@ -183,25 +183,24 @@ class op(bpy.types.Operator): bpy.context.scene.texToolsSettings.size[0] = self.size_x bpy.context.scene.texToolsSettings.size[1] = self.size_y - #Restore selection + # Restore selection utilities_uv.selection_restore() 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])) + bpy.ops.uv.cursor_set(location=Vector([0, 1])) elif mode == 'TR': - bpy.ops.uv.cursor_set(location=Vector([1,1])) + bpy.ops.uv.cursor_set(location=Vector([1, 1])) elif mode == 'BL': - bpy.ops.uv.cursor_set(location=Vector([0,0])) + bpy.ops.uv.cursor_set(location=Vector([0, 0])) elif mode == 'BR': - bpy.ops.uv.cursor_set(location=Vector([1,0])) + bpy.ops.uv.cursor_set(location=Vector([1, 0])) # Select all UV faces bpy.ops.uv.select_all(action='SELECT') @@ -209,24 +208,25 @@ def resize_uv(self, context, mode, size_A, size_B): # 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) - + 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 )) + 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) + 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)) + utilities_texel.image_resize( + image, int(size_B.x), int(size_B.y)) else: # No Image assigned @@ -234,20 +234,21 @@ def resize_image(context, mode, size_A, size_B): # 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 + 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.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 = 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) @@ -260,5 +261,4 @@ def resize_image(context, mode, size_A, size_B): utilities_texel.checker_images_cleanup() - -bpy.utils.register_class(op) \ No newline at end of file +bpy.utils.register_class(op) diff --git a/utilities_bake.py b/utilities_bake.py index 8e3e1f1..b061b35 100644 --- a/utilities_bake.py +++ b/utilities_bake.py @@ -12,27 +12,26 @@ from . import utilities_color # from . import op_bake -keywords_low = ['lowpoly','low','lowp','lp','lo','l'] -keywords_high = ['highpoly','high','highp','hp','hi','h'] -keywords_cage = ['cage','c'] -keywords_float = ['floater','float','f'] - -split_chars = [' ','_','.','-'] +keywords_low = ['lowpoly', 'low', 'lowp', 'lp', 'lo', 'l'] +keywords_high = ['highpoly', 'high', 'highp', 'hp', 'hi', 'h'] +keywords_cage = ['cage', 'c'] +keywords_float = ['floater', 'float', 'f'] +split_chars = [' ', '_', '.', '-'] class BakeMode: - material = "" #Material name from external blend file + 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): + 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 @@ -44,7 +43,6 @@ class BakeMode: self.use_project = use_project - def on_select_bake_mode(mode): print("Mode changed {}".format(mode)) @@ -81,8 +79,6 @@ def store_bake_settings(): 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: @@ -96,7 +92,6 @@ def store_bake_settings(): # obj.cycles_visibility.shadow = False - def restore_bake_settings(): # Render Settings if settings.bake_render_engine != '': @@ -111,59 +106,59 @@ def restore_bake_settings(): # obj.cycles_visibility.shadow = True - stored_materials = {} stored_material_faces = {} + + def store_materials_clear(): stored_materials.clear() stored_material_faces.clear() - def store_materials(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) + 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); + bm = bmesh.from_edit_mesh(obj.data) - # for each slot backup the material + # 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] ) - + 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)) + print("- Store {} = {}".format(obj.name, slot.material.name)) # 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); + 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_","") + material.name = material.name.replace("backup_", "") obj.material_slots[index].material = material # Face material indexies @@ -180,12 +175,11 @@ def restore_materials(): 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(): + if len(name) >= 4 and name[-4] == '.' and name[-3].isdigit() and name[-2].isdigit() and name[-1].isdigit(): return name[:-4] return name @@ -202,7 +196,6 @@ def get_set_name_base(obj): return remove_digits(obj.name).lower() - def get_set_name(obj): # Get Basic name name = get_set_name_base(obj) @@ -210,7 +203,7 @@ def get_set_name(obj): # Split by ' ','_','.' etc. split = name.lower() for char in split_chars: - split = split.replace(char,' ') + split = split.replace(char, ' ') strings = split.split(' ') # Remove all keys from name @@ -231,7 +224,6 @@ def get_set_name(obj): return "_".join(new_strings) - def get_object_type(obj): name = get_set_name_base(obj) @@ -239,11 +231,11 @@ def get_object_type(obj): # Detect by name pattern split = name.lower() for char in split_chars: - split = split.replace(char,' ') + split = split.replace(char, ' ') strings = split.split(' ') # Detect float, more rare than low - for string in strings: + for string in strings: for key in keywords_float: if key == string: return 'float' @@ -257,33 +249,28 @@ def get_object_type(obj): 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 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' - # if nothing was detected, assume its low return 'low' - def get_baked_images(sets): images = [] for set in sets: @@ -295,19 +282,18 @@ def get_baked_images(sets): 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: + if len(groups) == 0: groups.append([obj]) else: isFound = False @@ -330,10 +316,9 @@ def get_bake_sets(): if key == get_set_name(group[0]): sorted_groups.append(group) break - - groups = sorted_groups - # print("Keys: "+", ".join(keys)) + groups = sorted_groups + # print("Keys: "+", ".join(keys)) bake_sets = [] for group in groups: @@ -351,19 +336,17 @@ def get_bake_sets(): 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 + objects_low = [] # low poly geometry + objects_cage = [] # Cage low poly geometry + objects_high = [] # High poly geometry + objects_float = [] # Floating geometry name = "" has_issues = False @@ -390,14 +373,12 @@ class BakeSet: break - def setup_vertex_color_selection(obj): bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.select_all(action='DESELECT') - obj.select_set( state = True, view_layer = None) + obj.select_set(state=True, view_layer=None) bpy.context.view_layer.objects.active = obj - bpy.ops.object.mode_set(mode='VERTEX_PAINT') @@ -415,26 +396,24 @@ def setup_vertex_color_selection(obj): bpy.ops.object.mode_set(mode='OBJECT') - def setup_vertex_color_dirty(obj): print("setup_vertex_color_dirty {}".format(obj.name)) bpy.ops.object.select_all(action='DESELECT') - obj.select_set( state = True, view_layer = None) + 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, + # 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 + loop[colorLayer] = color obj.data.update() # Back to object mode @@ -443,13 +422,11 @@ def setup_vertex_color_dirty(obj): 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) + 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') @@ -482,10 +459,9 @@ def setup_vertex_color_id_material(obj): 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) + obj.select_set(state=True, view_layer=None) bpy.context.view_layer.objects.active = obj bpy.ops.object.mode_set(mode='EDIT') @@ -510,9 +486,9 @@ def setup_vertex_color_id_element(obj): groups.append(linked) # Color each group - for i in range(0,len(groups)): + for i in range(0, len(groups)): color = utilities_color.get_color_id(i, len(groups)) - color = utilities_color.safe_color( color ) + color = utilities_color.safe_color(color) for face in groups[i]: for loop in face.loops: loop[colorLayer] = color @@ -535,7 +511,6 @@ def get_image_material(image): 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 @@ -551,17 +526,17 @@ def get_image_material(image): node_image.image = image material.node_tree.nodes.active = node_image - #Base Diffuse BSDF + # 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 = material.node_tree.nodes.new( + "ShaderNodeNormalMap") node_normal_map.name = "normal_map" # Tangent or World space @@ -571,10 +546,12 @@ def get_image_material(image): node_normal_map.space = 'WORLD' # image to normal_map link - material.node_tree.links.new(node_image.outputs[0], node_normal_map.inputs[1]) + 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]) + 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)) @@ -582,15 +559,16 @@ def get_image_material(image): 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]) + 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] @@ -600,6 +578,6 @@ def get_image_material(image): texture.image = image slot = material.texture_slot.add() slot.texture = texture - slot.mapping = 'FLAT' + slot.mapping = 'FLAT' # return material diff --git a/utilities_color.py b/utilities_color.py index b2ce576..0650871 100644 --- a/utilities_color.py +++ b/utilities_color.py @@ -43,28 +43,26 @@ 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) - if material.use_nodes and bpy.context.scene.render.engine == 'CYCLES' or material.use_nodes and bpy.context.scene.render.engine == 'BLENDER_EEVEE' : + 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 - def get_material(index): name = get_name(index) # Material already exists? if name in bpy.data.materials: - material = bpy.data.materials[name]; + material = bpy.data.materials[name] # Check for incorrect matreials for current render engine if not material: @@ -77,7 +75,7 @@ def get_material(index): replace_material(index) else: - return material; + return material print("Could nt find {} , create a new one??".format(name)) @@ -86,7 +84,6 @@ def get_material(index): return material - # Replaace an existing material with a new one # This is sometimes necessary after switching the render engine def replace_material(index): @@ -96,11 +93,11 @@ def replace_material(index): # Check if material exists if name in bpy.data.materials: - material = bpy.data.materials[name]; + material = bpy.data.materials[name] # Collect material slots we have to re-assign slots = [] - for obj in bpy.context.view_layer.objects: + for obj in bpy.context.view_layer.objects: for slot in obj.material_slots: if slot.material == material: slots.append(slot) @@ -108,12 +105,11 @@ def replace_material(index): # 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; - + slot.material = material def create_material(index): @@ -125,17 +121,15 @@ def create_material(index): if bpy.context.scene.render.engine == 'CYCLES': # Cycles: prefer nodes as it simplifies baking - material.use_nodes = True + material.use_nodes = True return material - def get_name(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)) @@ -144,16 +138,15 @@ def get_color(index): 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) - + 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; + previous_mode = bpy.context.object.mode count = bpy.context.scene.texToolsSettings.color_ID_count # Verify enough material slots @@ -165,12 +158,11 @@ def validate_face_colors(obj): else: break - # TODO: Check face.material_index bpy.ops.object.mode_set(mode='EDIT') - bm = bmesh.from_edit_mesh(obj.data); + bm = bmesh.from_edit_mesh(obj.data) for face in bm.faces: - face.material_index%= count + face.material_index %= count obj.data.update() # Remove material slots that are not used @@ -179,19 +171,16 @@ def validate_face_colors(obj): 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.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) - 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)) @@ -205,23 +194,21 @@ def hex_to_color(hex): return tuple(fin) - def color_to_hex(color): rgb = [] for i in range(3): - rgb.append( pow(color[i] , 1.0/gamma) ) + rgb.append(pow(color[i], 1.0/gamma)) 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 + color.hsv = (index / (count)), 0.9, 1.0 + + return color diff --git a/utilities_ui.py b/utilities_ui.py index 3e4ad9c..4bed4ce 100644 --- a/utilities_ui.py +++ b/utilities_ui.py @@ -11,20 +11,21 @@ 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 @@ -33,11 +34,12 @@ def GetContextView3D(): for window in bpy.context.window_manager.windows: screen = window.screen for area in screen.areas: - if area.type == 'VIEW_3D': + 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 + 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 @@ -45,86 +47,80 @@ def GetContextViewUV(): for window in bpy.context.window_manager.windows: screen = window.screen for area in screen.areas: - if area.type == 'IMAGE_EDITOR': + 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 + 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') - def get_padding(): - size_min = min(bpy.context.scene.texToolsSettings.size[0],bpy.context.scene.texToolsSettings.size[1]) + 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 - + 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" - message : StringProperty() - + message: StringProperty() + 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 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()) - def register(): from bpy.types import Scene from bpy.props import StringProperty, EnumProperty - + print("_______REgister previews") # Operators @@ -135,19 +131,19 @@ def register(): # 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_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' + update=on_bakemode_set, + default='normal_tangent.png' ) - + def unregister(): print("_______UNregister previews") @@ -156,15 +152,14 @@ def unregister(): 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) - del bpy.types.Scene.TT_bake_mode - + + if __name__ == "__main__": register() -bpy.utils.register_class(op_popup) \ No newline at end of file +bpy.utils.register_class(op_popup)