PEP8 compliance

pull/1/head
Andrew Cassidy 4 years ago
parent f5252922b9
commit f60b85b477
No known key found for this signature in database
GPG Key ID: 963017B38FD477A1

@ -13,25 +13,26 @@ class op(bpy.types.Operator):
bl_label = "Align" bl_label = "Align"
bl_description = "Align vertices, edges or shells" bl_description = "Align vertices, edges or shells"
bl_options = {'REGISTER', 'UNDO'} bl_options = {'REGISTER', 'UNDO'}
direction : bpy.props.StringProperty(name="Direction", default="top") direction: bpy.props.StringProperty(name="Direction", default="top")
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
if not bpy.context.active_object: if not bpy.context.active_object:
return False return False
#Only in Edit mode # Only in Edit mode
if bpy.context.active_object.mode != 'EDIT': if bpy.context.active_object.mode != 'EDIT':
return False return False
#Only in UV editor mode # Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR': if bpy.context.area.type != 'IMAGE_EDITOR':
return False return False
#Requires UV map # Requires UV map
if not bpy.context.object.data.uv_layers: 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: if bpy.context.scene.tool_settings.use_uv_select_sync:
@ -39,27 +40,23 @@ class op(bpy.types.Operator):
return True return True
def execute(self, context): def execute(self, context):
align(context, self.direction) align(context, self.direction)
return {'FINISHED'} return {'FINISHED'}
def align(context, direction): def align(context, direction):
#Store selection # Store selection
utilities_uv.selection_store() utilities_uv.selection_store()
if bpy.context.tool_settings.transform_pivot_point != 'CURSOR': if bpy.context.tool_settings.transform_pivot_point != 'CURSOR':
bpy.context.tool_settings.transform_pivot_point = 'CURSOR' bpy.context.tool_settings.transform_pivot_point = 'CURSOR'
#B-Mesh # B-Mesh
obj = bpy.context.active_object obj = bpy.context.active_object
bm = bmesh.from_edit_mesh(obj.data); bm = bmesh.from_edit_mesh(obj.data)
uv_layers = bm.loops.layers.uv.verify(); uv_layers = bm.loops.layers.uv.verify()
if len(obj.data.uv_layers) == 0: if len(obj.data.uv_layers) == 0:
print("There is no UV channel or UV data set") 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 mode = bpy.context.scene.tool_settings.uv_select_mode
if mode == 'FACE' or mode == 'ISLAND': if mode == 'FACE' or mode == 'ISLAND':
print("____ Align Islands") print("____ Align Islands")
#Collect UV islands # Collect UV islands
islands = utilities_uv.getSelectionIslands() islands = utilities_uv.getSelectionIslands()
for island in islands: for island in islands:
bpy.ops.uv.select_all(action='DESELECT') bpy.ops.uv.select_all(action='DESELECT')
utilities_uv.set_selected_faces(island) utilities_uv.set_selected_faces(island)
bounds = utilities_uv.getSelectionBBox() bounds = utilities_uv.getSelectionBBox()
@ -84,13 +81,13 @@ def align(context, direction):
# print("Island "+str(len(island))+"x faces, delta: "+str(delta.y)) # print("Island "+str(len(island))+"x faces, delta: "+str(delta.y))
if direction == "bottom": if direction == "bottom":
delta = boundsAll['min'] - bounds['min'] delta = boundsAll['min'] - bounds['min']
bpy.ops.transform.translate(value=(0, delta.y, 0)) bpy.ops.transform.translate(value=(0, delta.y, 0))
elif direction == "top": elif direction == "top":
delta = boundsAll['max'] - bounds['max'] delta = boundsAll['max'] - bounds['max']
bpy.ops.transform.translate(value=(0, delta.y, 0)) bpy.ops.transform.translate(value=(0, delta.y, 0))
elif direction == "left": elif direction == "left":
delta = boundsAll['min'] - bounds['min'] delta = boundsAll['min'] - bounds['min']
bpy.ops.transform.translate(value=(delta.x, 0, 0)) bpy.ops.transform.translate(value=(delta.x, 0, 0))
elif direction == "right": elif direction == "right":
delta = boundsAll['max'] - bounds['max'] delta = boundsAll['max'] - bounds['max']
@ -98,7 +95,6 @@ def align(context, direction):
else: else:
print("Unkown direction: "+str(direction)) print("Unkown direction: "+str(direction))
elif mode == 'EDGE' or mode == 'VERTEX': elif mode == 'EDGE' or mode == 'VERTEX':
print("____ Align Verts") print("____ Align Verts")
@ -117,15 +113,10 @@ def align(context, direction):
elif direction == "right": elif direction == "right":
luv.uv[0] = boundsAll['max'].x luv.uv[0] = boundsAll['max'].x
bmesh.update_edit_mesh(obj.data) bmesh.update_edit_mesh(obj.data)
#Restore selection # Restore selection
utilities_uv.selection_restore() utilities_uv.selection_restore()
bpy.utils.register_class(op)
bpy.utils.register_class(op)

@ -8,19 +8,19 @@ from random import random
from . import utilities_ui from . import utilities_ui
from . import settings 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 # 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_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), 'cavity': ub.BakeMode('bake_cavity', type='EMIT', setVColor=ub.setup_vertex_color_dirty),
'paint_base': ub.BakeMode('bake_paint_base', type='EMIT'), 'paint_base': ub.BakeMode('bake_paint_base', type='EMIT'),
'dust': ub.BakeMode('bake_dust', type='EMIT', setVColor=ub.setup_vertex_color_dirty), '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_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), '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), 'selection': ub.BakeMode('bake_vertex_color', type='EMIT', color=(0, 0, 0, 1), setVColor=ub.setup_vertex_color_selection),
'diffuse': ub.BakeMode('', type='DIFFUSE'), 'diffuse': ub.BakeMode('', type='DIFFUSE'),
# 'displacment': ub.BakeMode('', type='DISPLACEMENT', use_project=True, color=(0, 0, 0, 1), engine='CYCLES'), # 'displacment': ub.BakeMode('', type='DISPLACEMENT', use_project=True, color=(0, 0, 0, 1), engine='CYCLES'),
'ao': ub.BakeMode('', type='AO', color=(1, 1, 1, 0), params=["bake_samples"], engine='CYCLES'), 'ao': 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"]) '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) # 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['bevel_mask'] = ub.BakeMode('bake_bevel_mask', type='EMIT', color=(
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"]) 0, 0, 0, 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['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): class op(bpy.types.Operator):
@ -53,43 +55,45 @@ class op(bpy.types.Operator):
bake_mode = utilities_ui.get_bake_mode() bake_mode = utilities_ui.get_bake_mode()
if bake_mode not in modes: 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 return
# Store Selection # Store Selection
selected_objects = [obj for obj in bpy.context.selected_objects] selected_objects = [obj for obj in bpy.context.selected_objects]
active_object = bpy.context.view_layer.objects.active active_object = bpy.context.view_layer.objects.active
ub.store_bake_settings() ub.store_bake_settings()
# Render sets # Render sets
bake( bake(
self = self, self=self,
mode = bake_mode, mode=bake_mode,
size = bpy.context.scene.texToolsSettings.size, size=bpy.context.scene.texToolsSettings.size,
bake_single = bpy.context.scene.texToolsSettings.bake_force_single, bake_single=bpy.context.scene.texToolsSettings.bake_force_single,
sampling_scale = int(bpy.context.scene.texToolsSettings.bake_sampling), sampling_scale=int(
samples = bpy.context.scene.texToolsSettings.bake_samples, bpy.context.scene.texToolsSettings.bake_sampling),
ray_distance = bpy.context.scene.texToolsSettings.bake_ray_distance samples=bpy.context.scene.texToolsSettings.bake_samples,
ray_distance=bpy.context.scene.texToolsSettings.bake_ray_distance
) )
# Restore selection # Restore selection
ub.restore_bake_settings() ub.restore_bake_settings()
bpy.ops.object.select_all(action='DESELECT') bpy.ops.object.select_all(action='DESELECT')
for obj in selected_objects: for obj in selected_objects:
obj.select_set( state = True, view_layer = None) obj.select_set(state=True, view_layer=None)
if active_object: if active_object:
bpy.context.view_layer.objects.active = active_object bpy.context.view_layer.objects.active = active_object
return {'FINISHED'} return {'FINISHED'}
def bake(self, mode, size, bake_single, sampling_scale, samples, ray_distance): def bake(self, mode, size, bake_single, sampling_scale, samples, ray_distance):
print("Bake '{}'".format(mode)) print("Bake '{}'".format(mode))
bpy.context.scene.render.engine = modes[mode].engine #Switch render engine # Switch render engine
bpy.context.scene.render.engine = modes[mode].engine
# Disable edit mode # Disable edit mode
if bpy.context.view_layer.objects.active != None and bpy.context.object.mode != 'OBJECT': 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_width = sampling_scale * size[0]
render_height = sampling_scale * size[1] render_height = sampling_scale * size[1]
for s in range(0,len(sets)): for s in range(0, len(sets)):
set = sets[s] set = sets[s]
# Get image name # Get image name
name_texture = "{}_{}".format(set.name, mode) name_texture = "{}_{}".format(set.name, mode)
if bake_single: 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)) path = bpy.path.abspath("//{}.tga".format(name_texture))
# Requires 1+ low poly objects # Requires 1+ low poly objects
if len(set.objects_low) == 0: 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 return
# Check for UV maps # Check for UV maps
for obj in set.objects_low: for obj in set.objects_low:
if not obj.data.uv_layers or len(obj.data.uv_layers) == 0: 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 return
# Check for cage inconsistencies # Check for cage inconsistencies
if len(set.objects_cage) > 0 and (len(set.objects_low) != len(set.objects_cage)): 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 return
# Get Materials # Get Materials
@ -136,7 +144,6 @@ def bake(self, mode, size, bake_single, sampling_scale, samples, ray_distance):
else: else:
material_empty = bpy.data.materials.new(name="TT_bake_node") material_empty = bpy.data.materials.new(name="TT_bake_node")
# Assign Materials to Objects # Assign Materials to Objects
if (len(set.objects_high) + len(set.objects_float)) == 0: if (len(set.objects_high) + len(set.objects_float)) == 0:
# Low poly bake: Assign material to lowpoly # 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_vertex_color(mode, obj)
assign_material(mode, obj, material_loaded) assign_material(mode, obj, material_loaded)
# Setup Image # Setup Image
is_clear = (not bake_single) or (bake_single and s==0) is_clear = (not bake_single) or (bake_single and s == 0)
image = setup_image(mode, name_texture, render_width, render_height, path, is_clear) image = setup_image(mode, name_texture, render_width,
render_height, path, is_clear)
# Assign bake node to Material # Assign bake node to Material
setup_image_bake_node(set.objects_low[0], image) setup_image_bake_node(set.objects_low[0], image)
print("Bake '{}' = {}".format(set.name, path)) 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 # Bake each low poly object in this set
for i in range(len(set.objects_low)): for i in range(len(set.objects_low)):
obj_low = set.objects_low[i] 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 # Disable hide render
obj_low.hide_render = False obj_low.hide_render = False
bpy.ops.object.select_all(action='DESELECT') 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 bpy.context.view_layer.objects.active = obj_low
if modes[mode].engine == 'BLENDER_EEVEE': 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 area.spaces[0].image = image
# bpy.data.screens['UV Editing'].areas[1].spaces[0].image = image # bpy.data.screens['UV Editing'].areas[1].spaces[0].image = image
bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.mode_set(mode='OBJECT')
for obj_high in (set.objects_high): 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( cycles_bake(
mode, mode,
bpy.context.scene.texToolsSettings.padding, bpy.context.scene.texToolsSettings.padding,
sampling_scale, sampling_scale,
samples, samples,
ray_distance, ray_distance,
len(set.objects_high) > 0, len(set.objects_high) > 0,
obj_cage obj_cage
) )
# Bake Floaters seperate bake # Bake Floaters seperate bake
if len(set.objects_float) > 0: if len(set.objects_float) > 0:
bpy.ops.object.select_all(action='DESELECT') bpy.ops.object.select_all(action='DESELECT')
for obj_high in (set.objects_float): for obj_high in (set.objects_float):
obj_high.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) obj_low.select_set(state=True, view_layer=None)
cycles_bake( cycles_bake(
mode, mode,
0, 0,
sampling_scale, sampling_scale,
samples, samples,
ray_distance, ray_distance,
len(set.objects_float) > 0, len(set.objects_float) > 0,
obj_cage 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: for obj_cage in set.objects_cage:
obj_cage.hide_render = False obj_cage.hide_render = False
# Downsample image? # Downsample image?
if not bake_single or (bake_single and s == len(sets)-1): if not bake_single or (bake_single and s == len(sets)-1):
# When baking single, only downsample on last bake # When baking single, only downsample on last bake
if render_width != size[0] or render_height != size[1]: 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 # Apply composite nodes on final image result
if modes[mode].composite: 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() # image.save()
@ -248,8 +254,6 @@ def bake(self, mode, size, bake_single, sampling_scale, samples, ray_distance):
ub.restore_materials() ub.restore_materials()
def apply_composite(image, scene_name, size): def apply_composite(image, scene_name, size):
previous_scene = bpy.context.window.scene previous_scene = bpy.context.window.scene
@ -258,31 +262,33 @@ def apply_composite(image, scene_name, size):
if scene_name in bpy.data.scenes: if scene_name in bpy.data.scenes:
scene = bpy.data.scenes[scene_name] scene = bpy.data.scenes[scene_name]
else: else:
path = os.path.join(os.path.dirname(__file__), "resources/compositing.blend")+"\\Scene\\" path = os.path.join(os.path.dirname(__file__),
bpy.ops.wm.append(filename=scene_name, directory=path, link=False, autoselect=False) "resources/compositing.blend")+"\\Scene\\"
bpy.ops.wm.append(filename=scene_name, directory=path,
link=False, autoselect=False)
scene = bpy.data.scenes[scene_name] scene = bpy.data.scenes[scene_name]
if scene: if scene:
# Switch scene # Switch scene
bpy.context.window.scene = scene bpy.context.window.scene = scene
#Setup composite nodes for Curvature # Setup composite nodes for Curvature
if "Image" in scene.node_tree.nodes: if "Image" in scene.node_tree.nodes:
scene.node_tree.nodes["Image"].image = image scene.node_tree.nodes["Image"].image = image
if "Offset" in scene.node_tree.nodes: if "Offset" in scene.node_tree.nodes:
scene.node_tree.nodes["Offset"].outputs[0].default_value = size 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 # Render image
bpy.ops.render.render(use_viewport=False) bpy.ops.render.render(use_viewport=False)
# Get last images of viewer node and render result # Get last images of viewer node and render result
image_viewer_node = get_last_item("Viewer Node", bpy.data.images) image_viewer_node = get_last_item("Viewer Node", bpy.data.images)
image_render_result = get_last_item("Render Result", 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.pixels = image_viewer_node.pixels[:]
image.update() image.update()
@ -291,14 +297,13 @@ def apply_composite(image, scene_name, size):
if image_render_result: if image_render_result:
bpy.data.images.remove(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 bpy.context.window.scene = previous_scene
# Delete compositing scene # Delete compositing scene
bpy.data.scenes.remove(scene) bpy.data.scenes.remove(scene)
def get_last_item(key_name, collection): def get_last_item(key_name, collection):
# bpy.data.images # bpy.data.images
# Get last image of a series, e.g. .001, .002, 003 # 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: if key_name in item.name:
keys.append(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: if len(keys) > 0:
return collection[keys[-1]] return collection[keys[-1]]
@ -315,8 +320,6 @@ def get_last_item(key_name, collection):
return None return None
def setup_image(mode, name, width, height, path, is_clear): def setup_image(mode, name, width, height, path, is_clear):
image = None image = None
@ -331,7 +334,6 @@ def setup_image(mode, name, width, height, path, is_clear):
# bpy.data.images[name].update() # bpy.data.images[name].update()
# if bpy.data.images[name].has_data == False: # if bpy.data.images[name].has_data == False:
# Previous image does not have data, remove first # Previous image does not have data, remove first
# print("Image pointer exists but no data "+name) # 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: if name not in bpy.data.images:
# Create new image with 32 bit float # Create new image with 32 bit float
is_float_32 = bpy.context.preferences.addons["textools"].preferences.bake_32bit_float == '32' 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: if "_normal_" in image.name:
image.colorspace_settings.name = 'Non-Color' image.colorspace_settings.name = 'Non-Color'
else: else:
image.colorspace_settings.name = 'sRGB' image.colorspace_settings.name = 'sRGB'
else: else:
# Reuse existing Image # Reuse existing Image
image = bpy.data.images[name] 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_color = modes[mode].color
image.generated_type = 'BLANK' image.generated_type = 'BLANK'
image.file_format = 'TARGA' image.file_format = 'TARGA'
# TODO: Verify that the path exists # TODO: Verify that the path exists
@ -373,11 +374,10 @@ def setup_image(mode, name, width, height, path, is_clear):
return image return image
def setup_image_bake_node(obj, image): def setup_image_bake_node(obj, image):
if len(obj.data.materials) <= 0: 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: else:
for slot in obj.material_slots: for slot in obj.material_slots:
if slot.material: if slot.material:
@ -397,46 +397,45 @@ def setup_image_bake_node(obj, image):
tree.nodes.active = node tree.nodes.active = node
def assign_vertex_color(mode, obj): def assign_vertex_color(mode, obj):
if modes[mode].setVColor: if modes[mode].setVColor:
modes[mode].setVColor(obj) modes[mode].setVColor(obj)
def assign_material(mode, obj, material_bake=None, material_empty=None): def assign_material(mode, obj, material_bake=None, material_empty=None):
ub.store_materials(obj) ub.store_materials(obj)
bpy.context.view_layer.objects.active = obj 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 # Select All faces
bpy.ops.object.mode_set(mode='EDIT') 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] faces = [face for face in bm.faces if face.select]
bpy.ops.mesh.select_all(action='SELECT') bpy.ops.mesh.select_all(action='SELECT')
if material_bake: if material_bake:
# Setup properties of bake materials # Setup properties of bake materials
if mode == 'wireframe': if mode == 'wireframe':
if "Value" in material_bake.node_tree.nodes: 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 mode == 'bevel_mask':
if "Bevel" in material_bake.node_tree.nodes: 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 material_bake.node_tree.nodes["Bevel"].samples = bpy.context.scene.texToolsSettings.bake_bevel_samples
if mode == 'normal_tangent_bevel': if mode == 'normal_tangent_bevel':
if "Bevel" in material_bake.node_tree.nodes: 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 material_bake.node_tree.nodes["Bevel"].samples = bpy.context.scene.texToolsSettings.bake_bevel_samples
if mode == 'normal_object_bevel': if mode == 'normal_object_bevel':
if "Bevel" in material_bake.node_tree.nodes: 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 material_bake.node_tree.nodes["Bevel"].samples = bpy.context.scene.texToolsSettings.bake_bevel_samples
# Don't apply in diffuse mode # Don't apply in diffuse mode
if mode != 'diffuse': if mode != 'diffuse':
if material_bake: if material_bake:
@ -450,11 +449,11 @@ def assign_material(mode, obj, material_bake=None, material_empty=None):
bpy.ops.object.material_slot_assign() bpy.ops.object.material_slot_assign()
elif material_empty: elif material_empty:
#Assign material_empty if no material available # Assign material_empty if no material available
if len(obj.material_slots) == 0: if len(obj.material_slots) == 0:
obj.data.materials.append(material_empty) 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.material_slots[0].material = material_empty
obj.active_material_index = 0 obj.active_material_index = 0
bpy.ops.object.material_slot_assign() 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') bpy.ops.object.mode_set(mode='OBJECT')
def get_material(mode): def get_material(mode):
if modes[mode].material == "": if modes[mode].material == "":
return None # No material setup requires return None # No material setup requires
# Find or load material # Find or load material
name = modes[mode].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: 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)) print("Get mat {}\n{}".format(mode, path))
if bpy.data.materials.get(name) is None: if bpy.data.materials.get(name) is None:
print("Material not yet loaded: "+mode) 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) return bpy.data.materials.get(name)
def cycles_bake(mode, padding, sampling_scale, samples, ray_distance, is_multi, obj_cage): 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 # # Snippet: https://gist.github.com/AndrewRayCode/760c4634a77551827de41ed67585064b
# bpy.context.scene.render.bake_margin = padding # 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() # 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': 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_r = 'POS_X'
bpy.context.scene.render.bake.normal_g = 'POS_Z' bpy.context.scene.render.bake.normal_g = 'POS_Z'
bpy.context.scene.render.bake.normal_b = 'NEG_Y' 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: if obj_cage is None:
# Bake with Cage # Bake with Cage
bpy.ops.object.bake( bpy.ops.object.bake(
type=modes[mode].type, type=modes[mode].type,
use_clear=False, use_clear=False,
cage_extrusion=ray_distance, cage_extrusion=ray_distance,
use_selected_to_active=is_multi, use_selected_to_active=is_multi,
normal_space=modes[mode].normal_space normal_space=modes[mode].normal_space
) )
else: else:
# Bake without Cage # Bake without Cage
bpy.ops.object.bake( bpy.ops.object.bake(
type=modes[mode].type, type=modes[mode].type,
use_clear=False, use_clear=False,
cage_extrusion=ray_distance, cage_extrusion=ray_distance,
use_selected_to_active=is_multi, use_selected_to_active=is_multi,
normal_space=modes[mode].normal_space, normal_space=modes[mode].normal_space,
#Use Cage and assign object # Use Cage and assign object
use_cage=True, use_cage=True,
cage_object=obj_cage.name 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) bpy.utils.register_class(op)

@ -23,15 +23,12 @@ class op(bpy.types.Operator):
return True return True
def execute(self, context): def execute(self, context):
explode(self) explode(self)
return {'FINISHED'} return {'FINISHED'}
def explode(self): def explode(self):
sets = settings.sets sets = settings.sets
@ -40,13 +37,14 @@ def explode(self):
avg_side = 0 avg_side = 0
for set in sets: for set in sets:
set_bounds[set] = get_bbox_set(set) 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'].x
avg_side+=set_bounds[set]['size'].y avg_side += set_bounds[set]['size'].y
avg_side+=set_bounds[set]['size'].z 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_set_volume = sorted(set_volume.items(), key=operator.itemgetter(1))
sorted_sets = [item[0] for item in sorted_set_volume] sorted_sets = [item[0] for item in sorted_set_volume]
@ -54,26 +52,23 @@ def explode(self):
# All combined bounding boxes # All combined bounding boxes
bbox_all = merge_bounds(list(set_bounds.values())) 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 # Offset sets into their direction
dir_offset_last_bbox = {} dir_offset_last_bbox = {}
for i in range(0,6): for i in range(0, 6):
dir_offset_last_bbox[i] = bbox_max #bbox_all dir_offset_last_bbox[i] = bbox_max # bbox_all
bpy.context.scene.frame_start = 0 bpy.context.scene.frame_start = 0
bpy.context.scene.frame_end = frame_range bpy.context.scene.frame_end = frame_range
bpy.context.scene.frame_current = 0 bpy.context.scene.frame_current = 0
# Process each set # Process each set
for set in sorted_sets: for set in sorted_sets:
if set_bounds[set] != bbox_max: if set_bounds[set] != bbox_max:
delta = set_bounds[set]['center'] - bbox_all['center'] 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): 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? # Which Direction?
delta_max = max(abs(delta.x), abs(delta.y), abs(delta.z)) delta_max = max(abs(delta.x), abs(delta.y), abs(delta.z))
direction = [0,0,0] direction = [0, 0, 0]
if delta_max > 0: if delta_max > 0:
for i in range(0,3): for i in range(0, 3):
if abs(delta[i]) == delta_max: if abs(delta[i]) == delta_max:
direction[i] = delta[i]/abs(delta[i]) direction[i] = delta[i]/abs(delta[i])
else: else:
direction[i] = 0 direction[i] = 0
else: else:
# Default when not delta offset was measure move up # 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])) delta = Vector((direction[0], direction[1], direction[2]))
@ -101,26 +96,26 @@ def offset_set(set, delta, margin, dir_offset_last_bbox):
# Calculate Offset # Calculate Offset
bbox = get_bbox_set(set) bbox = get_bbox_set(set)
bbox_last = dir_offset_last_bbox[key] bbox_last = dir_offset_last_bbox[key]
offset = Vector((0,0,0)) offset = Vector((0, 0, 0))
if delta.x == 1: 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: 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: 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: 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: 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: elif delta.z == -1:
offset = delta * -( bbox_last['min'].z - bbox['max'].z ) offset = delta * -(bbox_last['min'].z - bbox['max'].z)
# Add margin # Add margin
offset+= delta * margin offset += delta * margin
# Offset items # Offset items
# https://blenderartists.org/forum/showthread.php?237761-Blender-2-6-Set-keyframes-using-Python-script # 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) dir_offset_last_bbox[key] = get_bbox_set(set)
def get_delta_key(delta): def get_delta_key(delta):
# print("Get key {} is: {}".format(delta, delta.y == -1 )) # print("Get key {} is: {}".format(delta, delta.y == -1 ))
if delta.x == -1: if delta.x == -1:
@ -162,39 +155,36 @@ def get_delta_key(delta):
return 5 return 5
def merge_bounds(bounds): def merge_bounds(bounds):
box_min = bounds[0]['min'].copy() box_min = bounds[0]['min'].copy()
box_max = bounds[0]['max'].copy() box_max = bounds[0]['max'].copy()
for bbox in bounds: for bbox in bounds:
# box_min.x = -8 # box_min.x = -8
box_min.x = min(box_min.x, bbox['min'].x) box_min.x = min(box_min.x, bbox['min'].x)
box_min.y = min(box_min.y, bbox['min'].y) box_min.y = min(box_min.y, bbox['min'].y)
box_min.z = min(box_min.z, bbox['min'].z) box_min.z = min(box_min.z, bbox['min'].z)
box_max.x = max(box_max.x, bbox['max'].x) box_max.x = max(box_max.x, bbox['max'].x)
box_max.y = max(box_max.y, bbox['max'].y) box_max.y = max(box_max.y, bbox['max'].y)
box_max.z = max(box_max.z, bbox['max'].z) box_max.z = max(box_max.z, bbox['max'].z)
return { return {
'min':box_min, 'min': box_min,
'max':box_max, 'max': box_max,
'size':(box_max-box_min), 'size': (box_max-box_min),
'center':box_min+(box_max-box_min)/2 'center': box_min+(box_max-box_min)/2
} }
def get_bbox_set(set): def get_bbox_set(set):
objects = set.objects_low + set.objects_high + set.objects_cage objects = set.objects_low + set.objects_high + set.objects_cage
bounds = [] bounds = []
for obj in objects: for obj in objects:
bounds.append( get_bbox(obj) ) bounds.append(get_bbox(obj))
return merge_bounds(bounds) return merge_bounds(bounds)
def get_bbox(obj): def get_bbox(obj):
corners = [obj.matrix_world @ Vector(corner) for corner in obj.bound_box] 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.x = min(box_min.x, corner.x)
box_min.y = min(box_min.y, corner.y) box_min.y = min(box_min.y, corner.y)
box_min.z = min(box_min.z, corner.z) box_min.z = min(box_min.z, corner.z)
box_max.x = max(box_max.x, corner.x) box_max.x = max(box_max.x, corner.x)
box_max.y = max(box_max.y, corner.y) box_max.y = max(box_max.y, corner.y)
box_max.z = max(box_max.z, corner.z) box_max.z = max(box_max.z, corner.z)
return { return {
'min':box_min, 'min': box_min,
'max':box_max, 'max': box_max,
'size':(box_max-box_min), 'size': (box_max-box_min),
'center':box_min+(box_max-box_min)/2 'center': box_min+(box_max-box_min)/2
} }
bpy.utils.register_class(op)
bpy.utils.register_class(op)

@ -7,25 +7,25 @@ from math import pi
from . import utilities_color from . import utilities_color
class op(bpy.types.Operator): class op(bpy.types.Operator):
bl_idname = "uv.textools_color_from_directions" bl_idname = "uv.textools_color_from_directions"
bl_label = "Color Directions" bl_label = "Color Directions"
bl_description = "Assign a color ID to different face directions" bl_description = "Assign a color ID to different face directions"
bl_options = {'REGISTER', 'UNDO'} bl_options = {'REGISTER', 'UNDO'}
directions : bpy.props.EnumProperty(items= directions: bpy.props.EnumProperty(items=[('2', '2', 'Top & Bottom, Sides'),
[('2', '2', 'Top & Bottom, Sides'), ('3', '3', 'Top & Bottom, Left & Right, Front & Back'),
('3', '3', 'Top & Bottom, Left & Right, Front & Back'), ('4', '4', 'Top, Left & Right, Front & Back, Bottom'),
('4', '4', 'Top, Left & Right, Front & Back, Bottom'), ('6', '6', 'All sides')],
('6', '6', 'All sides')], name="Directions",
name = "Directions", default='3'
default = '3' )
)
def invoke(self, context, event): def invoke(self, context, event):
wm = context.window_manager wm = context.window_manager
return wm.invoke_props_dialog(self) return wm.invoke_props_dialog(self)
# def draw(self, context): # def draw(self, context):
# layout = self.layout # layout = self.layout
# layout.prop(self, "directions") # layout.prop(self, "directions")
@ -44,43 +44,39 @@ class op(bpy.types.Operator):
if bpy.context.active_object.type != 'MESH': if bpy.context.active_object.type != 'MESH':
return False return False
#Only in UV editor mode # Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR': if bpy.context.area.type != 'IMAGE_EDITOR':
return False return False
return True return True
def execute(self, context): def execute(self, context):
color_elements(self, context) color_elements(self, context)
return {'FINISHED'} return {'FINISHED'}
def color_elements(self, context): def color_elements(self, context):
obj = bpy.context.active_object obj = bpy.context.active_object
# Setup Edit & Face mode # Setup Edit & Face mode
if obj.mode != 'EDIT': if obj.mode != 'EDIT':
bpy.ops.object.mode_set(mode='EDIT') bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE') bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE')
# 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 = { face_directions = {
'top':[], 'top': [],
'bottom':[], 'bottom': [],
'left':[], 'left': [],
'right':[], 'right': [],
'front':[], 'front': [],
'back':[] 'back': []
} }
print("Directions {}".format(self.directions)) print("Directions {}".format(self.directions))
for face in bm.faces: for face in bm.faces:
print("face {} n: {}".format(face.index, face.normal)) print("face {} n: {}".format(face.index, face.normal))
# Find dominant direction # Find dominant direction
@ -114,7 +110,8 @@ def color_elements(self, context):
if self.directions == '2': if self.directions == '2':
groups.append(face_directions['top']+face_directions['bottom']) 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': if self.directions == '3':
groups.append(face_directions['top']+face_directions['bottom']) groups.append(face_directions['top']+face_directions['bottom'])
groups.append(face_directions['left']+face_directions['right']) groups.append(face_directions['left']+face_directions['right'])
@ -136,8 +133,8 @@ def color_elements(self, context):
index_color = 0 index_color = 0
for group in groups: for group in groups:
# # rebuild bmesh data (e.g. left edit mode previous loop) # # rebuild bmesh data (e.g. left edit mode previous loop)
bm = bmesh.from_edit_mesh(bpy.context.active_object.data); bm = bmesh.from_edit_mesh(bpy.context.active_object.data)
if hasattr(bm.faces, "ensure_lookup_table"): if hasattr(bm.faces, "ensure_lookup_table"):
bm.faces.ensure_lookup_table() bm.faces.ensure_lookup_table()
# Select group # Select group
@ -148,7 +145,8 @@ def color_elements(self, context):
# Assign to selection # Assign to selection
bpy.ops.uv.textools_color_assign(index=index_color) 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') bpy.ops.object.mode_set(mode='OBJECT')
utilities_color.validate_face_colors(obj) utilities_color.validate_face_colors(obj)
@ -203,4 +201,5 @@ def color_elements(self, context):
utilities_color.validate_face_colors(obj) utilities_color.validate_face_colors(obj)
''' '''
bpy.utils.register_class(op)
bpy.utils.register_class(op)

@ -7,12 +7,12 @@ from math import pi
from . import utilities_color from . import utilities_color
class op(bpy.types.Operator): class op(bpy.types.Operator):
bl_idname = "uv.textools_color_from_materials" bl_idname = "uv.textools_color_from_materials"
bl_label = "Color Elements" bl_label = "Color Elements"
bl_description = "Assign a color ID to each mesh material slot" bl_description = "Assign a color ID to each mesh material slot"
bl_options = {'REGISTER', 'UNDO'} bl_options = {'REGISTER', 'UNDO'}
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
@ -28,21 +28,20 @@ class op(bpy.types.Operator):
if bpy.context.active_object.type != 'MESH': if bpy.context.active_object.type != 'MESH':
return False return False
#Only in UV editor mode # Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR': if bpy.context.area.type != 'IMAGE_EDITOR':
return False return False
return True return True
def execute(self, context): def execute(self, context):
color_materials(self, context) color_materials(self, context)
return {'FINISHED'} return {'FINISHED'}
def color_materials(self, context): def color_materials(self, context):
obj = bpy.context.active_object obj = bpy.context.active_object
for s in range(len(obj.material_slots)): for s in range(len(obj.material_slots)):
slot = obj.material_slots[s] slot = obj.material_slots[s]
if slot.material: if slot.material:
@ -50,4 +49,5 @@ def color_materials(self, context):
utilities_color.validate_face_colors(obj) utilities_color.validate_face_colors(obj)
bpy.utils.register_class(op)
bpy.utils.register_class(op)

@ -15,28 +15,29 @@ class op(bpy.types.Operator):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
#Only in UV editor mode # Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR': if bpy.context.area.type != 'IMAGE_EDITOR':
return False return False
return True return True
def execute(self, context): def execute(self, context):
export_colors(self, context) export_colors(self, context)
return {'FINISHED'} return {'FINISHED'}
def export_colors(self, context): def export_colors(self, context):
hex_colors = [] hex_colors = []
for i in range(bpy.context.scene.texToolsSettings.color_ID_count): for i in range(bpy.context.scene.texToolsSettings.color_ID_count):
color = getattr(bpy.context.scene.texToolsSettings, "color_ID_color_{}".format(i)) color = getattr(bpy.context.scene.texToolsSettings,
hex_colors.append( utilities_color.color_to_hex( color) ) "color_ID_color_{}".format(i))
hex_colors.append(utilities_color.color_to_hex(color))
bpy.context.window_manager.clipboard = ", ".join(hex_colors) 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) bpy.utils.register_class(op)

@ -7,13 +7,14 @@ from math import pi
from . import utilities_color from . import utilities_color
class op(bpy.types.Operator): class op(bpy.types.Operator):
bl_idname = "uv.textools_color_select" bl_idname = "uv.textools_color_select"
bl_label = "Assign Color" bl_label = "Assign Color"
bl_description = "Select faces by this color" bl_description = "Select faces by this color"
bl_options = {'REGISTER', 'UNDO'} bl_options = {'REGISTER', 'UNDO'}
index : bpy.props.IntProperty(description="Color Index", default=0) index: bpy.props.IntProperty(description="Color Index", default=0)
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
@ -30,40 +31,42 @@ class op(bpy.types.Operator):
if bpy.context.active_object.type != 'MESH': if bpy.context.active_object.type != 'MESH':
return False return False
#Only in UV editor mode # Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR': if bpy.context.area.type != 'IMAGE_EDITOR':
return False return False
return True return True
def execute(self, context): def execute(self, context):
select_color(self, context, self.index) select_color(self, context, self.index)
return {'FINISHED'} return {'FINISHED'}
def select_color(self, context, index): def select_color(self, context, index):
print("Color select "+str(index) ) print("Color select "+str(index))
obj = bpy.context.active_object obj = bpy.context.active_object
# Check for missing slots, materials,.. # Check for missing slots, materials,..
if index >= len(obj.material_slots): 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 return
if not obj.material_slots[index].material: if not obj.material_slots[index].material:
self.report({'ERROR_INVALID_INPUT'}, "No material found for material slot '{}'".format(index) ) self.report({'ERROR_INVALID_INPUT'},
return "No material found for material slot '{}'".format(index))
return
if bpy.context.active_object.mode != 'EDIT': if bpy.context.active_object.mode != 'EDIT':
bpy.ops.object.mode_set(mode='EDIT') bpy.ops.object.mode_set(mode='EDIT')
# Select faces # 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') bpy.ops.mesh.select_all(action='DESELECT')
for face in bm.faces: for face in bm.faces:
if face.material_index == index: if face.material_index == index:
face.select = True face.select = True
bpy.utils.register_class(op)
bpy.utils.register_class(op)

@ -6,71 +6,64 @@ import operator
from mathutils import Vector from mathutils import Vector
from collections import defaultdict from collections import defaultdict
from itertools import chain # 'flattens' collection of iterables from itertools import chain # 'flattens' collection of iterables
from . import utilities_uv from . import utilities_uv
class op(bpy.types.Operator): class op(bpy.types.Operator):
bl_idname = "uv.textools_edge_split_bevel" bl_idname = "uv.textools_edge_split_bevel"
bl_label = "Split Bevel" bl_label = "Split Bevel"
bl_description = "..." bl_description = "..."
bl_options = {'REGISTER', 'UNDO'} bl_options = {'REGISTER', 'UNDO'}
radius : bpy.props.FloatProperty( radius: bpy.props.FloatProperty(
name = "Space", name="Space",
description = "Space for split bevel", description="Space for split bevel",
default = 0.015, default=0.015,
min = 0, min=0,
max = 0.35 max=0.35
) )
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
if not bpy.context.active_object: if not bpy.context.active_object:
return False return False
#Only in Edit mode # Only in Edit mode
if bpy.context.active_object.mode != 'EDIT': if bpy.context.active_object.mode != 'EDIT':
return False return False
#Requires UV map # Requires UV map
if not bpy.context.object.data.uv_layers: if not bpy.context.object.data.uv_layers:
return False return False
if bpy.context.scene.tool_settings.use_uv_select_sync: if bpy.context.scene.tool_settings.use_uv_select_sync:
return False return False
#Only in UV editor mode # Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR': if bpy.context.area.type != 'IMAGE_EDITOR':
return False return False
return True return True
def execute(self, context): def execute(self, context):
main(self, self.radius) main(self, self.radius)
return {'FINISHED'} return {'FINISHED'}
def main(self, radius): def main(self, radius):
#Store selection # Store selection
utilities_uv.selection_store() utilities_uv.selection_store()
print("____________\nedge split UV sharp edges {}".format(radius)) print("____________\nedge split UV sharp edges {}".format(radius))
obj = bpy.context.object
bm = bmesh.from_edit_mesh(bpy.context.active_object.data)
uv_layers = bm.loops.layers.uv.verify()
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() islands = utilities_uv.getSelectionIslands()
# Collect UV to Vert # Collect UV to Vert
vert_to_uv = utilities_uv.get_vert_to_uv(bm, uv_layers) 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)) # print("Hard edge: {} - {}".format(edge.verts[0].index, edge.verts[1].index))
edges.append(edge) edges.append(edge)
# Get vert rails to slide # Get vert rails to slide
vert_rails = get_vert_edge_rails(edges) vert_rails = get_vert_edge_rails(edges)
# Get left and right faces # Get left and right faces
edge_face_pairs = get_edge_face_pairs(edges) edge_face_pairs = get_edge_face_pairs(edges)
print("Vert rails: {}x".format(len(vert_rails))) print("Vert rails: {}x".format(len(vert_rails)))
# for vert in vert_rails: # for vert in vert_rails:
# print(".. v.idx {} = {}x".format(vert.index, len(vert_rails[vert]) )) # print(".. v.idx {} = {}x".format(vert.index, len(vert_rails[vert]) ))
vert_processed = [] vert_processed = []
vert_uv_pos = [] vert_uv_pos = []
@ -104,77 +94,79 @@ def main(self, radius):
if len(edge_face_pairs[edge]) == 2: if len(edge_face_pairs[edge]) == 2:
v0 = edge.verts[0] v0 = edge.verts[0]
v1 = edge.verts[1] v1 = edge.verts[1]
f0 = edge_face_pairs[edge][0] f0 = edge_face_pairs[edge][0]
f1 = edge_face_pairs[edge][1] f1 = edge_face_pairs[edge][1]
# v0 # v0
if v0 not in vert_processed: if v0 not in vert_processed:
vert_processed.append(v0) vert_processed.append(v0)
faces, origin, delta = slide_uvs(v0, edge, f0, edges, vert_rails, vert_to_uv) faces, origin, delta = slide_uvs(
vert_uv_pos.append( {"v":v0, "f":f0, "origin":origin, "delta":delta, "faces":faces} ) 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) faces, origin, delta = slide_uvs(
vert_uv_pos.append( {"v":v0, "f":f1, "origin":origin, "delta":delta, "faces":faces} ) v0, edge, f1, edges, vert_rails, vert_to_uv)
vert_uv_pos.append(
{"v": v0, "f": f1, "origin": origin, "delta": delta, "faces": faces})
# V1 # V1
if v1 not in vert_processed: if v1 not in vert_processed:
vert_processed.append(v1) vert_processed.append(v1)
faces, origin, delta = slide_uvs(v1, edge, f0, edges, vert_rails, vert_to_uv) faces, origin, delta = slide_uvs(
vert_uv_pos.append( {"v":v1, "f":f0, "origin":origin, "delta":delta, "faces":faces} ) v1, edge, f0, edges, vert_rails, vert_to_uv)
vert_uv_pos.append(
faces, origin, delta = slide_uvs(v1, edge, f1, edges, vert_rails, vert_to_uv) {"v": v1, "f": f0, "origin": origin, "delta": delta, "faces": faces})
vert_uv_pos.append( {"v":v1, "f":f1, "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: for item in vert_uv_pos:
v = item["v"] v = item["v"]
for face in item["faces"]: for face in item["faces"]:
if v in face.verts: if v in face.verts:
for loop in face.loops: for loop in face.loops:
if loop.vert == v: 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 f in faces:
# for loop in f.loops: # for loop in f.loops:
# if loop.vert == vert: # if loop.vert == vert:
# loop[uv_layers].uv= vert_to_uv[vert][0].uv + item["delta"] * radius/2 # loop[uv_layers].uv= vert_to_uv[vert][0].uv + item["delta"] * radius/2
# for loop in face.loops: # for loop in face.loops:
# if loop.vert == vert: # if loop.vert == vert:
# loop[uv_layers].uv+= avg_uv_delta # loop[uv_layers].uv+= avg_uv_delta
# Restore selection
#Restore selection
utilities_uv.selection_restore() utilities_uv.selection_restore()
def slide_uvs(vert, edge, face, edges, vert_rails, vert_to_uv): def slide_uvs(vert, edge, face, edges, vert_rails, vert_to_uv):
def IS_DEBUG(): def IS_DEBUG():
return vert.index == 64 and edge.verts[0].index == 64 and edge.verts[1].index == 63 return vert.index == 64 and edge.verts[0].index == 64 and edge.verts[1].index == 63
A = edge.verts[0] A = edge.verts[0]
B = edge.verts[1] B = edge.verts[1]
A_links, B_links = get_edge_prev_next(edge, edges) A_links, B_links = get_edge_prev_next(edge, edges)
verts_edges = {edge.verts[0], edge.verts[1]} verts_edges = {edge.verts[0], edge.verts[1]}
for v in A_links: for v in A_links:
verts_edges.add( v ) verts_edges.add(v)
for v in B_links: for v in B_links:
verts_edges.add( v ) verts_edges.add(v)
if IS_DEBUG(): if IS_DEBUG():
print("\r") print("\r")
print("Edge {} <--> {} ({})".format(edge.verts[0].index, edge.verts[1].index , vert.index)) print(
"Edge {} <--> {} ({})".format(edge.verts[0].index, edge.verts[1].index, vert.index))
# Collect faces of this side # Collect faces of this side
@ -207,12 +199,12 @@ def slide_uvs(vert, edge, face, edges, vert_rails, vert_to_uv):
if IS_DEBUG(): if IS_DEBUG():
print(" Faces {}x = {}".format(len(faces), [f.index for f in faces])) print(" Faces {}x = {}".format(len(faces), [f.index for f in faces]))
# Get all face edges that could be valid rails # 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 # The verts influencing the offset
verts = [A,B] verts = [A, B]
if vert == A: if vert == A:
verts.extend(B_links) verts.extend(B_links)
elif vert == B: 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(" Verts: {}x = {}".format(len(verts), [v.index for v in verts]))
print(" Rails:") print(" Rails:")
delta = Vector((0, 0))
delta = Vector((0,0))
count = 0.0 count = 0.0
for v in verts: for v in verts:
rails = [e for e in vert_rails[v] if e in face_edges] rails = [e for e in vert_rails[v] if e in face_edges]
if IS_DEBUG(): 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: for e in rails:
# determine order # determine order
@ -248,7 +239,7 @@ def slide_uvs(vert, edge, face, edges, vert_rails, vert_to_uv):
delta += (uv1-uv0).normalized() delta += (uv1-uv0).normalized()
count += 1.0 count += 1.0
delta/=count delta /= count
if IS_DEBUG(): if IS_DEBUG():
print("\r") 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 # loop[uv_layers].uv+= avg_uv_delta
''' '''
def slide_face_uvs(uv_layers, edge, vert, face, radius, vert_to_uv): def slide_face_uvs(uv_layers, edge, vert, face, radius, vert_to_uv):
avg_target = Vector((0,0)) 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) # Get all rails (max 3x: current, before and after)
rails = [e for v in verts_edges for e in vert_rails[v]] 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): def get_edge_prev_next(edge, edges):
A = edge.verts[0] A = edge.verts[0]
B = edge.verts[1] B = edge.verts[1]
# print(" get_edge_prev_next {}x edges".format(len(edges))) # 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] # 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] # 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] # 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] A_extends = [v2 for v1 in edge.verts for e in v1.link_edges for v2 in e.verts if e !=
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] edge and e in edges and v2 not in edge.verts and v1 != A]
B_extends = [v2 for v1 in edge.verts for e in v1.link_edges for v2 in e.verts if e !=
edge and e in edges and v2 not in edge.verts and v1 != B]
return A_extends, B_extends return A_extends, B_extends
@ -342,7 +326,6 @@ def get_edge_face_pairs(edges):
return edge_faces return edge_faces
def get_vert_edge_rails(edges): def get_vert_edge_rails(edges):
vert_rails = {} vert_rails = {}
@ -359,9 +342,9 @@ def get_vert_edge_rails(edges):
for e in face.edges: for e in face.edges:
if e not in edges and len(e.link_faces) > 0: if e not in edges and len(e.link_faces) > 0:
if v0 not in vert_rails: if v0 not in vert_rails:
vert_rails[ v0 ] = [] vert_rails[v0] = []
if v1 not in vert_rails: if v1 not in vert_rails:
vert_rails[ v1 ] = [] vert_rails[v1] = []
if v0 in e.verts and e not in vert_rails[v0]: if v0 in e.verts and e not in vert_rails[v0]:
vert_rails[v0].append(e) vert_rails[v0].append(e)

@ -8,15 +8,16 @@ from math import pi
from . import utilities_uv from . import utilities_uv
class op(bpy.types.Operator): class op(bpy.types.Operator):
bl_idname = "uv.textools_island_align_edge" bl_idname = "uv.textools_island_align_edge"
bl_label = "Align Island by Edge" bl_label = "Align Island by Edge"
bl_description = "Align the island by selected edge" bl_description = "Align the island by selected edge"
bl_options = {'REGISTER', 'UNDO'} bl_options = {'REGISTER', 'UNDO'}
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
#Only in UV editor mode # Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR': if bpy.context.area.type != 'IMAGE_EDITOR':
return False return False
@ -26,51 +27,49 @@ class op(bpy.types.Operator):
if bpy.context.active_object.type != 'MESH': if bpy.context.active_object.type != 'MESH':
return False return False
#Only in Edit mode # Only in Edit mode
if bpy.context.active_object.mode != 'EDIT': if bpy.context.active_object.mode != 'EDIT':
return False return False
if bpy.context.scene.tool_settings.use_uv_select_sync: if bpy.context.scene.tool_settings.use_uv_select_sync:
return False return False
#Requires UV map # Requires UV map
if not bpy.context.object.data.uv_layers: if not bpy.context.object.data.uv_layers:
return False return False
# Requires UV Edge select mode # Requires UV Edge select mode
if bpy.context.scene.tool_settings.uv_select_mode != 'EDGE': if bpy.context.scene.tool_settings.uv_select_mode != 'EDGE':
return False return False
return True return True
def execute(self, context): def execute(self, context):
#Store selection # Store selection
utilities_uv.selection_store() utilities_uv.selection_store()
main(context) main(context)
#Restore selection # Restore selection
utilities_uv.selection_restore() utilities_uv.selection_restore()
return {'FINISHED'} return {'FINISHED'}
def main(context): def main(context):
print("Executing operator_island_align_edge") print("Executing operator_island_align_edge")
bm = bmesh.from_edit_mesh(bpy.context.active_object.data) bm = bmesh.from_edit_mesh(bpy.context.active_object.data)
uv_layers = bm.loops.layers.uv.verify() uv_layers = bm.loops.layers.uv.verify()
faces_selected = []; faces_selected = []
for face in bm.faces: for face in bm.faces:
if face.select: if face.select:
for loop in face.loops: for loop in face.loops:
if loop[uv_layers].select: if loop[uv_layers].select:
faces_selected.append(face) faces_selected.append(face)
break break
print("faces_selected: "+str(len(faces_selected))) print("faces_selected: "+str(len(faces_selected)))
# Collect 2 uv verts for each island # Collect 2 uv verts for each island
@ -91,11 +90,11 @@ def main(context):
if face in faces_unparsed: if face in faces_unparsed:
bpy.ops.uv.select_all(action='DESELECT') bpy.ops.uv.select_all(action='DESELECT')
face_uvs[face][0].select = True; face_uvs[face][0].select = True
bpy.ops.uv.select_linked()#Extend selection bpy.ops.uv.select_linked() # Extend selection
#Collect faces # Collect faces
faces_island = [face]; faces_island = [face]
for f in faces_unparsed: for f in faces_unparsed:
if f != face and f.select and f.loops[0][uv_layers].select: if f != face and f.select and f.loops[0][uv_layers].select:
print("append "+str(f.index)) print("append "+str(f.index))
@ -103,14 +102,15 @@ def main(context):
for f in faces_island: for f in faces_island:
faces_unparsed.remove(f) faces_unparsed.remove(f)
#Assign Faces to island # Assign Faces to island
faces_islands[face] = faces_island faces_islands[face] = faces_island
print("Sets: {}x".format(len(faces_islands))) print("Sets: {}x".format(len(faces_islands)))
# Align each island to its edges # Align each island to its edges
for face in faces_islands: 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): 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 loop[uv_layers].select = True
diff = uv_vert1 - uv_vert0 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() bpy.ops.uv.select_linked()
@ -136,7 +136,8 @@ def align_island(uv_vert0, uv_vert1, faces):
if angle >= (math.pi/4): if angle >= (math.pi/4):
angle = angle - (math.pi/2) 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) bpy.utils.register_class(op)

@ -18,9 +18,10 @@ class op(bpy.types.Operator):
bl_description = "Rotates UV islands to minimal bounds and sorts them horizontal or vertical" bl_description = "Rotates UV islands to minimal bounds and sorts them horizontal or vertical"
bl_options = {'REGISTER', 'UNDO'} bl_options = {'REGISTER', 'UNDO'}
is_vertical: bpy.props.BoolProperty(
is_vertical : bpy.props.BoolProperty(description="Vertical or Horizontal orientation", default=True) description="Vertical or Horizontal orientation", default=True)
padding : bpy.props.FloatProperty(description="Padding between UV islands", default=0.05) padding: bpy.props.FloatProperty(
description="Padding between UV islands", default=0.05)
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
@ -31,25 +32,25 @@ class op(bpy.types.Operator):
if bpy.context.active_object.type != 'MESH': if bpy.context.active_object.type != 'MESH':
return False return False
#Only in Edit mode # Only in Edit mode
if bpy.context.active_object.mode != 'EDIT': if bpy.context.active_object.mode != 'EDIT':
return False return False
#Only in UV editor mode # Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR': if bpy.context.area.type != 'IMAGE_EDITOR':
return False return False
#Requires UV map # Requires UV map
if not bpy.context.object.data.uv_layers: 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: if bpy.context.scene.tool_settings.use_uv_select_sync:
return False return False
return True return True
def execute(self, context): def execute(self, context):
main(context, self.is_vertical, self.padding) main(context, self.is_vertical, self.padding)
return {'FINISHED'} return {'FINISHED'}
@ -57,49 +58,48 @@ class op(bpy.types.Operator):
def main(context, isVertical, padding): def main(context, isVertical, padding):
print("Executing IslandsAlignSort main {}".format(padding)) print("Executing IslandsAlignSort main {}".format(padding))
#Store selection # Store selection
utilities_uv.selection_store() utilities_uv.selection_store()
if bpy.context.tool_settings.transform_pivot_point != 'CURSOR': if bpy.context.tool_settings.transform_pivot_point != 'CURSOR':
bpy.context.tool_settings.transform_pivot_point = 'CURSOR' 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': if bpy.context.scene.tool_settings.uv_select_mode is not 'FACE' or 'ISLAND':
bpy.context.scene.tool_settings.uv_select_mode = 'FACE' bpy.context.scene.tool_settings.uv_select_mode = 'FACE'
bm = bmesh.from_edit_mesh(bpy.context.active_object.data); bm = bmesh.from_edit_mesh(bpy.context.active_object.data)
uv_layers = bm.loops.layers.uv.verify(); uv_layers = bm.loops.layers.uv.verify()
boundsAll = utilities_uv.getSelectionBBox() boundsAll = utilities_uv.getSelectionBBox()
islands = utilities_uv.getSelectionIslands() 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 = {} allBounds = {}
print("Islands: "+str(len(islands))+"x") print("Islands: "+str(len(islands))+"x")
bpy.context.window_manager.progress_begin(0, len(islands)) bpy.context.window_manager.progress_begin(0, len(islands))
#Rotate to minimal bounds # Rotate to minimal bounds
for i in range(0, len(islands)): for i in range(0, len(islands)):
alignIslandMinimalBounds(uv_layers, islands[i]) alignIslandMinimalBounds(uv_layers, islands[i])
# Collect BBox sizes # Collect BBox sizes
bounds = utilities_uv.getSelectionBBox() bounds = utilities_uv.getSelectionBBox()
allSizes[i] = max(bounds['width'], bounds['height']) + i*0.000001;#Make each size unique allSizes[i] = max(bounds['width'], bounds['height']) + \
allBounds[i] = bounds; i*0.000001 # Make each size unique
allBounds[i] = bounds
print("Rotate compact: "+str(allSizes[i])) print("Rotate compact: "+str(allSizes[i]))
bpy.context.window_manager.progress_update(i) bpy.context.window_manager.progress_update(i)
bpy.context.window_manager.progress_end() bpy.context.window_manager.progress_end()
# Position by sorted size in row
#Position by sorted size in row # Sort by values, store tuples
sortedSizes = sorted(allSizes.items(), key=operator.itemgetter(1))#Sort by values, store tuples sortedSizes = sorted(allSizes.items(), key=operator.itemgetter(1))
sortedSizes.reverse() sortedSizes.reverse()
offset = 0.0 offset = 0.0
for sortedSize in sortedSizes: for sortedSize in sortedSizes:
@ -107,23 +107,24 @@ def main(context, isVertical, padding):
island = islands[index] island = islands[index]
bounds = allBounds[index] bounds = allBounds[index]
#Select Island # Select Island
bpy.ops.uv.select_all(action='DESELECT') bpy.ops.uv.select_all(action='DESELECT')
utilities_uv.set_selected_faces(island) utilities_uv.set_selected_faces(island)
#Offset Island # Offset Island
if(isVertical): 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)) bpy.ops.transform.translate(value=(delta.x, delta.y-offset, 0))
offset += bounds['height']+padding offset += bounds['height']+padding
else: else:
print("Horizontal") 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)) bpy.ops.transform.translate(value=(delta.x+offset, delta.y, 0))
offset += bounds['width']+padding offset += bounds['width']+padding
# Restore selection
#Restore selection
utilities_uv.selection_restore() utilities_uv.selection_restore()
@ -133,13 +134,14 @@ def alignIslandMinimalBounds(uv_layers, faces):
utilities_uv.set_selected_faces(faces) utilities_uv.set_selected_faces(faces)
steps = 8 steps = 8
angle = 45; # Starting Angle, half each step angle = 45 # Starting Angle, half each step
bboxPrevious = utilities_uv.getSelectionBBox() bboxPrevious = utilities_uv.getSelectionBBox()
for i in range(0, steps): for i in range(0, steps):
# Rotate right # 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() bbox = utilities_uv.getSelectionBBox()
if i == 0: if i == 0:
@ -147,25 +149,28 @@ def alignIslandMinimalBounds(uv_layers, faces):
sizeB = bbox['width'] * bbox['height'] sizeB = bbox['width'] * bbox['height']
if abs(bbox['width'] - bbox['height']) <= 0.0001 and sizeA < sizeB: if abs(bbox['width'] - bbox['height']) <= 0.0001 and sizeA < sizeB:
# print("Already squared") # print("Already squared")
bpy.ops.transform.rotate(value=(-angle * math.pi / 180), orient_axis='Z') bpy.ops.transform.rotate(
break; value=(-angle * math.pi / 180), orient_axis='Z')
break
if bbox['minLength'] < bboxPrevious['minLength']: if bbox['minLength'] < bboxPrevious['minLength']:
bboxPrevious = bbox; # Success bboxPrevious = bbox # Success
else: else:
# Rotate Left # 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() bbox = utilities_uv.getSelectionBBox()
if bbox['minLength'] < bboxPrevious['minLength']: if bbox['minLength'] < bboxPrevious['minLength']:
bboxPrevious = bbox; # Success bboxPrevious = bbox # Success
else: else:
# Restore angle of this iteration # 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 angle = angle / 2
if bboxPrevious['width'] < bboxPrevious['height']: if bboxPrevious['width'] < bboxPrevious['height']:
bpy.ops.transform.rotate(value=(90 * math.pi / 180), orient_axis='Z') bpy.ops.transform.rotate(value=(90 * math.pi / 180), orient_axis='Z')
bpy.utils.register_class(op)
bpy.utils.register_class(op)

@ -6,13 +6,11 @@ import operator
from mathutils import Vector from mathutils import Vector
from collections import defaultdict from collections import defaultdict
from itertools import chain # 'flattens' collection of iterables from itertools import chain # 'flattens' collection of iterables
from . import utilities_uv from . import utilities_uv
class op(bpy.types.Operator): class op(bpy.types.Operator):
bl_idname = "uv.textools_island_align_world" bl_idname = "uv.textools_island_align_world"
bl_label = "Align World" bl_label = "Align World"
@ -34,57 +32,53 @@ class op(bpy.types.Operator):
if not bpy.context.active_object: if not bpy.context.active_object:
return False return False
#Only in Edit mode # Only in Edit mode
if bpy.context.active_object.mode != 'EDIT': if bpy.context.active_object.mode != 'EDIT':
return False return False
#Requires UV map # Requires UV map
if not bpy.context.object.data.uv_layers: if not bpy.context.object.data.uv_layers:
return False return False
if bpy.context.scene.tool_settings.use_uv_select_sync: if bpy.context.scene.tool_settings.use_uv_select_sync:
return False return False
#Only in UV editor mode # Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR': if bpy.context.area.type != 'IMAGE_EDITOR':
return False return False
return True return True
def execute(self, context): def execute(self, context):
main(self) main(self)
return {'FINISHED'} return {'FINISHED'}
def main(context): def main(context):
print("\n________________________\nis_global") print("\n________________________\nis_global")
#Store selection # Store selection
utilities_uv.selection_store() utilities_uv.selection_store()
bm = bmesh.from_edit_mesh(bpy.context.active_object.data) bm = bmesh.from_edit_mesh(bpy.context.active_object.data)
uv_layers = bm.loops.layers.uv.verify() 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': if bpy.context.scene.tool_settings.uv_select_mode is not 'FACE' or 'ISLAND':
bpy.context.scene.tool_settings.uv_select_mode = 'FACE' bpy.context.scene.tool_settings.uv_select_mode = 'FACE'
obj = bpy.context.object obj = bpy.context.object
bm = bmesh.from_edit_mesh(bpy.context.active_object.data); bm = bmesh.from_edit_mesh(bpy.context.active_object.data)
uv_layers = bm.loops.layers.uv.verify(); uv_layers = bm.loops.layers.uv.verify()
islands = utilities_uv.getSelectionIslands()
islands = utilities_uv.getSelectionIslands()
for faces in islands: for faces in islands:
# Get average viewport normal of UV island # Get average viewport normal of UV island
avg_normal = Vector((0,0,0)) avg_normal = Vector((0, 0, 0))
for face in faces: for face in faces:
avg_normal+=face.normal avg_normal += face.normal
avg_normal/=len(faces) avg_normal /= len(faces)
# avg_normal = (obj.matrix_world*avg_normal).normalized() # avg_normal = (obj.matrix_world*avg_normal).normalized()
@ -93,44 +87,43 @@ def main(context):
y = 1 y = 1
z = 2 z = 2
max_size = max(abs(avg_normal.x), abs(avg_normal.y), abs(avg_normal.z)) max_size = max(abs(avg_normal.x), abs(avg_normal.y), abs(avg_normal.z))
# Use multiple steps # Use multiple steps
for i in range(3): for i in range(3):
if(abs(avg_normal.x) == max_size): if(abs(avg_normal.x) == max_size):
print("x normal") 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): elif(abs(avg_normal.y) == max_size):
print("y normal") 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): elif(abs(avg_normal.z) == max_size):
print("z normal") print("z normal")
align_island(obj, bm, uv_layers, faces, x, y, False, avg_normal.z < 0) align_island(obj, bm, uv_layers, faces, x,
y, False, avg_normal.z < 0)
print("align island: faces {}x n:{}, max:{}".format(len(faces), avg_normal, max_size))
print("align island: faces {}x n:{}, max:{}".format(
len(faces), avg_normal, max_size))
#Restore selection # Restore selection
utilities_uv.selection_restore() utilities_uv.selection_restore()
def align_island(obj, bm, uv_layers, faces, x=0, y=1, flip_x=False, flip_y=False): def align_island(obj, bm, uv_layers, faces, x=0, y=1, flip_x=False, flip_y=False):
# Find lowest and highest verts # Find lowest and highest verts
minmax_val = [0,0] minmax_val = [0, 0]
minmax_vert = [None, None] minmax_vert = [None, None]
axis_names = ['x', 'y', 'z'] 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("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(" 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 # Collect UV to Vert
vert_to_uv = utilities_uv.get_vert_to_uv(bm, uv_layers) 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: for edge in face.edges:
if edge not in processed_edges: if edge not in processed_edges:
processed_edges.append(edge) 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)) max_side = max(abs(delta.x), abs(delta.y), abs(delta.z))
# Check edges dominant in active axis # Check edges dominant in active axis
if( abs(delta[x]) == max_side or abs(delta[y]) == max_side): if(abs(delta[x]) == max_side or abs(delta[y]) == max_side):
# if( abs(delta[y]) == max_side): # if( abs(delta[y]) == max_side):
edges.append(edge) edges.append(edge)
print("Edges {}x".format(len(edges))) print("Edges {}x".format(len(edges)))
avg_angle = 0 avg_angle = 0
for edge in edges: for edge in edges:
uv0 = vert_to_uv[ edge.verts[0] ][0] uv0 = vert_to_uv[edge.verts[0]][0]
uv1 = vert_to_uv[ edge.verts[1] ][0] uv1 = vert_to_uv[edge.verts[1]][0]
delta_verts = Vector(( delta_verts = Vector((
edge.verts[1].co[x] - edge.verts[0].co[x], edge.verts[1].co[x] - edge.verts[0].co[x],
edge.verts[1].co[y] - edge.verts[0].co[y] edge.verts[1].co[y] - edge.verts[0].co[y]
)) ))
if flip_x: if flip_x:
delta_verts.x = -edge.verts[1].co[x] + edge.verts[0].co[x] delta_verts.x = -edge.verts[1].co[x] + edge.verts[0].co[x]
if flip_y: if flip_y:
delta_verts.y = -edge.verts[1].co[y] + edge.verts[0].co[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_verts.y = edge.verts[0].co[y] - edge.verts[1].co[y]
delta_uvs = Vector(( delta_uvs = Vector((
uv1.uv.x - uv0.uv.x, 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 a0 = math.atan2(delta_verts.y, delta_verts.x) - math.pi/2
a1 = math.atan2(delta_uvs.y, delta_uvs.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 # 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)) # 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)) print("Turn {:.1f}".format(avg_angle * 180/math.pi))
bpy.ops.uv.select_all(action='DESELECT') bpy.ops.uv.select_all(action='DESELECT')
for face in faces: for face in faces:
for loop in face.loops: for loop in face.loops:
loop[uv_layers].select = True loop[uv_layers].select = True
bpy.context.tool_settings.transform_pivot_point = 'MEDIAN_POINT' bpy.context.tool_settings.transform_pivot_point = 'MEDIAN_POINT'
bpy.ops.transform.rotate(value=avg_angle, orient_axis='Z') 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) # 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 = [] # processed = []
''' '''
bpy.ops.uv.select_all(action='DESELECT') 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 # return
''' '''
bpy.utils.register_class(op) bpy.utils.register_class(op)

@ -8,12 +8,13 @@ from math import pi
from . import utilities_uv from . import utilities_uv
class op(bpy.types.Operator): class op(bpy.types.Operator):
bl_idname = "uv.textools_island_straighten_edge_loops" bl_idname = "uv.textools_island_straighten_edge_loops"
bl_label = "Straight edge loops" bl_label = "Straight edge loops"
bl_description = "Straighten edge loops of UV Island and relax rest" bl_description = "Straighten edge loops of UV Island and relax rest"
bl_options = {'REGISTER', 'UNDO'} bl_options = {'REGISTER', 'UNDO'}
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
@ -23,54 +24,47 @@ class op(bpy.types.Operator):
if bpy.context.active_object.type != 'MESH': if bpy.context.active_object.type != 'MESH':
return False return False
#Only in Edit mode # Only in Edit mode
if bpy.context.active_object.mode != 'EDIT': if bpy.context.active_object.mode != 'EDIT':
return False return False
#Only in UV editor mode # Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR': if bpy.context.area.type != 'IMAGE_EDITOR':
return False return False
#Requires UV map # Requires UV map
if not bpy.context.object.data.uv_layers: if not bpy.context.object.data.uv_layers:
return False return False
if bpy.context.scene.tool_settings.uv_select_mode != 'EDGE': if bpy.context.scene.tool_settings.uv_select_mode != 'EDGE':
return False return False
return True return True
def execute(self, context): def execute(self, context):
main(context) main(context)
return {'FINISHED'} return {'FINISHED'}
def main(context): def main(context):
print("____________________________") print("____________________________")
#Store selection # Store selection
utilities_uv.selection_store() utilities_uv.selection_store()
bm = bmesh.from_edit_mesh(bpy.context.active_object.data) bm = bmesh.from_edit_mesh(bpy.context.active_object.data)
uv_layers = bm.loops.layers.uv.verify() uv_layers = bm.loops.layers.uv.verify()
edges = utilities_uv.get_selected_uv_edges(bm, uv_layers) edges = utilities_uv.get_selected_uv_edges(bm, uv_layers)
islands = utilities_uv.getSelectionIslands() islands = utilities_uv.getSelectionIslands()
uvs = utilities_uv.get_selected_uvs(bm, uv_layers) 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 # Get island faces
# utilities_uv.selection_restore(bm, uv_layers) # utilities_uv.selection_restore(bm, uv_layers)
groups = get_edge_groups(bm, uv_layers, faces, edges, uvs) groups = get_edge_groups(bm, uv_layers, faces, edges, uvs)
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE') bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE')
@ -78,15 +72,10 @@ def main(context):
for face in faces: for face in faces:
face.select = True face.select = True
print("Edges {}x".format(len(edges))) print("Edges {}x".format(len(edges)))
print("Groups {}x".format(len(groups))) print("Groups {}x".format(len(groups)))
# Restore 3D face selection # Restore 3D face selection
# Restore UV seams and clear pins # Restore UV seams and clear pins
bpy.ops.uv.seams_from_islands() bpy.ops.uv.seams_from_islands()
@ -94,21 +83,16 @@ def main(context):
edge_sets = [] edge_sets = []
for edges in groups: 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) # straighten_edges(bm, uv_layers, edges, faces)
sorted_sets = sorted(edge_sets, key=lambda x: x.length, reverse=True) sorted_sets = sorted(edge_sets, key=lambda x: x.length, reverse=True)
for edge_set in sorted_sets: for edge_set in sorted_sets:
edge_set.straighten() edge_set.straighten()
#Restore selection # Restore selection
utilities_uv.selection_restore() utilities_uv.selection_restore()
class EdgeSet: class EdgeSet:
@ -136,11 +120,11 @@ class EdgeSet:
uv1 = self.vert_to_uv[e.verts[0]][0].uv uv1 = self.vert_to_uv[e.verts[0]][0].uv
uv2 = self.vert_to_uv[e.verts[1]][0].uv uv2 = self.vert_to_uv[e.verts[1]][0].uv
self.edge_length[e] = (uv2 - uv1).length self.edge_length[e] = (uv2 - uv1).length
self.length+=self.edge_length[e] self.length += self.edge_length[e]
def straighten(self): 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 # Get edge angles in UV space
angles = {} angles = {}
@ -148,17 +132,18 @@ class EdgeSet:
uv1 = self.vert_to_uv[edge.verts[0]][0].uv uv1 = self.vert_to_uv[edge.verts[0]][0].uv
uv2 = self.vert_to_uv[edge.verts[1]][0].uv uv2 = self.vert_to_uv[edge.verts[1]][0].uv
delta = uv2 - uv1 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): if angle >= (math.pi/4):
angle = angle - (math.pi/2) angle = angle - (math.pi/2)
angles[edge] = abs(angle) angles[edge] = abs(angle)
# print("Angle {:.2f} degr".format(angle * 180 / math.pi)) # print("Angle {:.2f} degr".format(angle * 180 / math.pi))
# Pick edge with least rotation offset to U or V axis # 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 # Rotate main edge to closest axis
uvs = [uv for v in edge_main.verts for uv in self.vert_to_uv[v]] uvs = [uv for v in edge_main.verts for uv in self.vert_to_uv[v]]
bpy.ops.uv.select_all(action='DESELECT') bpy.ops.uv.select_all(action='DESELECT')
@ -167,11 +152,12 @@ class EdgeSet:
uv1 = self.vert_to_uv[edge_main.verts[0]][0].uv uv1 = self.vert_to_uv[edge_main.verts[0]][0].uv
uv2 = self.vert_to_uv[edge_main.verts[1]][0].uv uv2 = self.vert_to_uv[edge_main.verts[1]][0].uv
diff = uv2 - uv1 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): if angle >= (math.pi/4):
angle = angle - (math.pi/2) angle = angle - (math.pi/2)
bpy.ops.uv.cursor_set(location=uv1 + diff/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 # Expand edges and straighten
count = len(self.edges) count = len(self.edges)
@ -179,11 +165,13 @@ class EdgeSet:
for i in range(count): for i in range(count):
if(len(processed) < len(self.edges)): if(len(processed) < len(self.edges)):
verts = set([v for e in processed for v in e.verts]) 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)] edges_expand = [e for e in self.edges if e not in processed and (
verts_ends = [v for e in edges_expand for v in e.verts if v in verts] 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: if len(edges_expand) == 0:
continue continue
@ -192,25 +180,31 @@ class EdgeSet:
# if edge.verts[0] in verts_ends and edge.verts[1] in verts_ends: # if edge.verts[0] in verts_ends and edge.verts[1] in verts_ends:
# print("Cancel at edge {}".format(edge.index)) # print("Cancel at edge {}".format(edge.index))
# return # 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] 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] v2 = [v for v in edge.verts if v not in verts_ends][0]
# direction # 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_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] 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]: 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))) print("verts_ends: {}x".format(len(verts_ends)))
processed.extend(edges_expand) processed.extend(edges_expand)
# Select edges # 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') bpy.ops.uv.select_all(action='DESELECT')
for uv in uvs: for uv in uvs:
uv.select = True uv.select = True
@ -221,16 +215,10 @@ class EdgeSet:
bpy.ops.uv.pin(clear=True) bpy.ops.uv.pin(clear=True)
def get_edge_groups(bm, uv_layers, faces, edges, uvs): def get_edge_groups(bm, uv_layers, faces, edges, uvs):
print("Get edge groups, edges {}x".format(len(edges))+"x") print("Get edge groups, edges {}x".format(len(edges))+"x")
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE') bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE')
unmatched = edges.copy() unmatched = edges.copy()
@ -253,7 +241,8 @@ def get_edge_groups(bm, uv_layers, faces, edges, uvs):
if e in unmatched: if e in unmatched:
unmatched.remove(e) 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 # return
# group = [edge] # group = [edge]
@ -262,11 +251,7 @@ def get_edge_groups(bm, uv_layers, faces, edges, uvs):
# unmatched.remove(e) # unmatched.remove(e)
# group.append(edge) # group.append(edge)
return groups return groups
bpy.utils.register_class(op) bpy.utils.register_class(op)

@ -9,8 +9,6 @@ import math
from . import utilities_meshtex from . import utilities_meshtex
class op(bpy.types.Operator): class op(bpy.types.Operator):
bl_idname = "uv.textools_meshtex_trim" bl_idname = "uv.textools_meshtex_trim"
bl_label = "Trim" bl_label = "Trim"
@ -21,12 +19,12 @@ class op(bpy.types.Operator):
def poll(cls, context): def poll(cls, context):
if not bpy.context.active_object or bpy.context.active_object.mode != 'OBJECT': if not bpy.context.active_object or bpy.context.active_object.mode != 'OBJECT':
return False return False
if len(bpy.context.selected_objects) >= 1: if len(bpy.context.selected_objects) >= 1:
# Find a UV mesh # Find a UV mesh
if utilities_meshtex.find_uv_mesh(bpy.context.selected_objects): if utilities_meshtex.find_uv_mesh(bpy.context.selected_objects):
# Find 1 or more meshes to wrap # 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 True
return False return False
@ -36,42 +34,40 @@ class op(bpy.types.Operator):
return {'FINISHED'} return {'FINISHED'}
def trim(self): def trim(self):
# Wrap the mesh texture around the # Wrap the mesh texture around the
print("Trim Mesh Texture :)") print("Trim Mesh Texture :)")
# Collect UV mesh # Collect UV mesh
obj_uv = utilities_meshtex.find_uv_mesh(bpy.context.selected_objects) obj_uv = utilities_meshtex.find_uv_mesh(bpy.context.selected_objects)
if not obj_uv: if not obj_uv:
self.report({'ERROR_INVALID_INPUT'}, "No UV mesh found" ) self.report({'ERROR_INVALID_INPUT'}, "No UV mesh found")
return return
# Collect texture meshes # 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: 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 return
# Setup Thickness # Setup Thickness
utilities_meshtex.uv_mesh_fit(obj_uv, obj_textures) utilities_meshtex.uv_mesh_fit(obj_uv, obj_textures)
# Apply bool modifier to trim # Apply bool modifier to trim
for obj in obj_textures: for obj in obj_textures:
name = "Trim UV" name = "Trim UV"
if name in obj.modifiers: 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 = obj.modifiers.new(name=name, type='BOOLEAN')
modifier_bool.object = obj_uv 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) bpy.utils.register_class(op)

@ -15,7 +15,7 @@ class op(bpy.types.Operator):
bl_label = "Rectify" bl_label = "Rectify"
bl_description = "Align selected faces or verts to rectangular distribution." bl_description = "Align selected faces or verts to rectangular distribution."
bl_options = {'REGISTER', 'UNDO'} bl_options = {'REGISTER', 'UNDO'}
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
if not bpy.context.active_object: if not bpy.context.active_object:
@ -32,34 +32,31 @@ class op(bpy.types.Operator):
return False return False
return True return True
def execute(self, context): def execute(self, context):
rectify(self, context) rectify(self, context)
return {'FINISHED'} return {'FINISHED'}
precision = 3 precision = 3
def rectify(self, context): def rectify(self, context):
obj = bpy.context.active_object obj = bpy.context.active_object
bm = bmesh.from_edit_mesh(obj.data) bm = bmesh.from_edit_mesh(obj.data)
uv_layers = bm.loops.layers.uv.verify() uv_layers = bm.loops.layers.uv.verify()
#Store selection # Store selection
utilities_uv.selection_store() utilities_uv.selection_store()
main(False) main(False)
#Restore selection # Restore selection
utilities_uv.selection_restore() utilities_uv.selection_restore()
def main(square = False, snapToClosest = False): def main(square=False, snapToClosest=False):
startTime = time.clock() startTime = time.clock()
obj = bpy.context.active_object obj = bpy.context.active_object
@ -68,69 +65,72 @@ def main(square = False, snapToClosest = False):
uv_layers = bm.loops.layers.uv.verify() uv_layers = bm.loops.layers.uv.verify()
# bm.faces.layers.tex.verify() # currently blender needs both layers. # bm.faces.layers.tex.verify() # currently blender needs both layers.
face_act = bm.faces.active face_act = bm.faces.active
targetFace = face_act targetFace = face_act
#if len(bm.faces) > allowedFaces: # if len(bm.faces) > allowedFaces:
# operator.report({'ERROR'}, "selected more than " +str(allowedFaces) +" allowed faces.") # 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 0: return if len(filteredVerts) is 1:
if len(filteredVerts) is 1:
SnapCursorToClosestSelected(filteredVerts) SnapCursorToClosestSelected(filteredVerts)
return return
cursorClosestTo = CursorClosestTo(filteredVerts) cursorClosestTo = CursorClosestTo(filteredVerts)
#line is selected #line is selected
if len(selFaces) is 0: if len(selFaces) is 0:
if snapToClosest is True: if snapToClosest is True:
SnapCursorToClosestSelected(filteredVerts) SnapCursorToClosestSelected(filteredVerts)
return return
VertsDictForLine(uv_layers, bm, filteredVerts, vertsDict) VertsDictForLine(uv_layers, bm, filteredVerts, vertsDict)
if AreVectsLinedOnAxis(filteredVerts) is False: if AreVectsLinedOnAxis(filteredVerts) is False:
ScaleTo0OnAxisAndCursor(filteredVerts, vertsDict, cursorClosestTo) ScaleTo0OnAxisAndCursor(filteredVerts, vertsDict, cursorClosestTo)
return SuccessFinished(me, startTime) return SuccessFinished(me, startTime)
MakeEqualDistanceBetweenVertsInLine(filteredVerts, vertsDict, cursorClosestTo) MakeEqualDistanceBetweenVertsInLine(
filteredVerts, vertsDict, cursorClosestTo)
return SuccessFinished(me, startTime) return SuccessFinished(me, startTime)
# else:
#else:
# active face checks
#active face checks
if targetFace is None or targetFace.select is False or len(targetFace.verts) is not 4: if targetFace is None or targetFace.select is False or len(targetFace.verts) is not 4:
targetFace = selFaces[0] targetFace = selFaces[0]
else: else:
for l in targetFace.loops: for l in targetFace.loops:
if l[uv_layers].select is False: if l[uv_layers].select is False:
targetFace = selFaces[0] targetFace = selFaces[0]
break break
ShapeFace(uv_layers, operator, targetFace, vertsDict, square) ShapeFace(uv_layers, operator, targetFace, vertsDict, square)
for nf in nonQuadFaces: for nf in nonQuadFaces:
for l in nf.loops: for l in nf.loops:
luv = l[uv_layers] luv = l[uv_layers]
luv.select = False luv.select = False
if square: FollowActiveUV(operator, me, targetFace, selFaces, 'EVEN') if square:
else: FollowActiveUV(operator, me, targetFace, selFaces) FollowActiveUV(operator, me, targetFace, selFaces, 'EVEN')
else:
FollowActiveUV(operator, me, targetFace, selFaces)
if noEdge is False: if noEdge is False:
#edge has ripped so we connect it back # edge has ripped so we connect it back
for ev in edgeVerts: for ev in edgeVerts:
key = (round(ev.uv.x, precision), round(ev.uv.y, precision)) key = (round(ev.uv.x, precision), round(ev.uv.y, precision))
if key in vertsDict: if key in vertsDict:
ev.uv = vertsDict[key][0].uv ev.uv = vertsDict[key][0].uv
ev.select = True ev.select = True
return SuccessFinished(me, startTime)
return SuccessFinished(me, startTime)
def ListsOfVerts(uv_layers, bm): def ListsOfVerts(uv_layers, bm):
@ -139,50 +139,52 @@ def ListsOfVerts(uv_layers, bm):
filteredVerts = [] filteredVerts = []
selFaces = [] selFaces = []
nonQuadFaces = [] nonQuadFaces = []
vertsDict = defaultdict(list) #dict vertsDict = defaultdict(list) # dict
for f in bm.faces: for f in bm.faces:
isFaceSel = True isFaceSel = True
facesEdgeVerts = [] facesEdgeVerts = []
if (f.select == False): if (f.select == False):
continue continue
#collect edge verts if any # collect edge verts if any
for l in f.loops: for l in f.loops:
luv = l[uv_layers] luv = l[uv_layers]
if luv.select is True: if luv.select is True:
facesEdgeVerts.append(luv) facesEdgeVerts.append(luv)
else: isFaceSel = False else:
isFaceSel = False
allEdgeVerts.extend(facesEdgeVerts) allEdgeVerts.extend(facesEdgeVerts)
if isFaceSel: if isFaceSel:
if len(f.verts) is not 4: if len(f.verts) is not 4:
nonQuadFaces.append(f) nonQuadFaces.append(f)
edgeVerts.extend(facesEdgeVerts) edgeVerts.extend(facesEdgeVerts)
else: else:
selFaces.append(f) selFaces.append(f)
for l in f.loops: for l in f.loops:
luv = l[uv_layers] luv = l[uv_layers]
x = round(luv.uv.x, precision) x = round(luv.uv.x, precision)
y = round(luv.uv.y, precision) y = round(luv.uv.y, precision)
vertsDict[(x, y)].append(luv) vertsDict[(x, y)].append(luv)
else: edgeVerts.extend(facesEdgeVerts) else:
edgeVerts.extend(facesEdgeVerts)
noEdge = False noEdge = False
if len(edgeVerts) is 0: if len(edgeVerts) is 0:
noEdge = True noEdge = True
edgeVerts.extend(allEdgeVerts) edgeVerts.extend(allEdgeVerts)
if len(selFaces) is 0: if len(selFaces) is 0:
for ev in edgeVerts: for ev in edgeVerts:
if ListQuasiContainsVect(filteredVerts, ev) is False: if ListQuasiContainsVect(filteredVerts, ev) is False:
filteredVerts.append(ev) filteredVerts.append(ev)
else: filteredVerts = edgeVerts else:
filteredVerts = edgeVerts
return edgeVerts, filteredVerts, selFaces, nonQuadFaces, vertsDict, noEdge
return edgeVerts, filteredVerts, selFaces, nonQuadFaces, vertsDict, noEdge
def ListQuasiContainsVect(list, vect): def ListQuasiContainsVect(list, vect):
@ -192,28 +194,23 @@ def ListQuasiContainsVect(list, vect):
return False return False
def SnapCursorToClosestSelected(filteredVerts): def SnapCursorToClosestSelected(filteredVerts):
#TODO: snap to closest selected # TODO: snap to closest selected
if len(filteredVerts) is 1: if len(filteredVerts) is 1:
SetAll2dCursorsTo(filteredVerts[0].uv.x, filteredVerts[0].uv.y) SetAll2dCursorsTo(filteredVerts[0].uv.x, filteredVerts[0].uv.y)
return
return
def VertsDictForLine(uv_layers, bm, selVerts, vertsDict): def VertsDictForLine(uv_layers, bm, selVerts, vertsDict):
for f in bm.faces: for f in bm.faces:
for l in f.loops: for l in f.loops:
luv = l[uv_layers] luv = l[uv_layers]
if luv.select is True: if luv.select is True:
x = round(luv.uv.x, precision) x = round(luv.uv.x, precision)
y = round(luv.uv.y, precision) y = round(luv.uv.y, precision)
vertsDict[(x, y)].append(luv)
vertsDict[(x, y)].append(luv)
def AreVectsLinedOnAxis(verts): def AreVectsLinedOnAxis(verts):
@ -227,191 +224,189 @@ def AreVectsLinedOnAxis(verts):
areLinedX = False areLinedX = False
if abs(valY - v.uv.y) > allowedError: if abs(valY - v.uv.y) > allowedError:
areLinedY = False 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 = 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] first = verts[0]
last = verts[len(verts)-1] last = verts[len(verts)-1]
if horizontal is None: if horizontal is None:
horizontal = True 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) slope = (last.uv.y - first.uv.y)/(last.uv.x - first.uv.x)
if (slope > 1) or (slope <-1): if (slope > 1) or (slope < -1):
horizontal = False horizontal = False
else: else:
horizontal = False horizontal = False
if horizontal is True: if horizontal is True:
if startv is None: if startv is None:
startv = first startv = first
SetAll2dCursorsTo(startv.uv.x, startv.uv.y) SetAll2dCursorsTo(startv.uv.x, startv.uv.y)
#scale to 0 on Y # scale to 0 on Y
ScaleTo0('Y') ScaleTo0('Y')
return return
else: else:
verts.sort(key=lambda x: x.uv[1]) #sort by .y verts.sort(key=lambda x: x.uv[1]) # sort by .y
verts.reverse() #reverse because y values drop from up to down verts.reverse() # reverse because y values drop from up to down
first = verts[0] first = verts[0]
last = verts[len(verts)-1] last = verts[len(verts)-1]
if startv is None: if startv is None:
startv = first startv = first
SetAll2dCursorsTo(startv.uv.x, startv.uv.y) SetAll2dCursorsTo(startv.uv.x, startv.uv.y)
#scale to 0 on X # scale to 0 on X
ScaleTo0('X') ScaleTo0('X')
return return
def SetAll2dCursorsTo(x, y):
def SetAll2dCursorsTo(x,y):
last_area = bpy.context.area.type last_area = bpy.context.area.type
bpy.context.area.type = 'IMAGE_EDITOR' bpy.context.area.type = 'IMAGE_EDITOR'
bpy.ops.uv.cursor_set(location=(x, y)) bpy.ops.uv.cursor_set(location=(x, y))
bpy.context.area.type = last_area bpy.context.area.type = last_area
return return
def CursorClosestTo(verts, allowedError=0.025):
def CursorClosestTo(verts, allowedError = 0.025):
ratioX, ratioY = ImageRatio() 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 min = 1000
minV = verts[0] minV = verts[0]
for v in verts: for v in verts:
if v is None: continue if v is None:
continue
for area in bpy.context.screen.areas: for area in bpy.context.screen.areas:
if area.type == 'IMAGE_EDITOR': if area.type == 'IMAGE_EDITOR':
loc = area.spaces[0].cursor_location 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): if (hyp < min):
min = hyp min = hyp
minV = v minV = v
if min is not 1000: if min is not 1000:
return minV return minV
return None return None
def SuccessFinished(me, startTime): def SuccessFinished(me, startTime):
#use for backtrack of steps # use for backtrack of steps
#bpy.ops.ed.undo_push() # bpy.ops.ed.undo_push()
bmesh.update_edit_mesh(me) bmesh.update_edit_mesh(me)
#elapsed = round(time.clock()-startTime, 2) #elapsed = round(time.clock()-startTime, 2)
#if (elapsed >= 0.05): operator.report({'INFO'}, "UvSquares finished, elapsed:", elapsed, "s.") #if (elapsed >= 0.05): operator.report({'INFO'}, "UvSquares finished, elapsed:", elapsed, "s.")
return return
def ShapeFace(uv_layers, operator, targetFace, vertsDict, square): def ShapeFace(uv_layers, operator, targetFace, vertsDict, square):
corners = [] corners = []
for l in targetFace.loops: for l in targetFace.loops:
luv = l[uv_layers] luv = l[uv_layers]
corners.append(luv) corners.append(luv)
if len(corners) is not 4: if len(corners) is not 4:
#operator.report({'ERROR'}, "bla") #operator.report({'ERROR'}, "bla")
return return
lucv, ldcv, rucv, rdcv = Corners(corners) lucv, ldcv, rucv, rdcv = Corners(corners)
cct = CursorClosestTo([lucv, ldcv, rdcv, rucv]) cct = CursorClosestTo([lucv, ldcv, rdcv, rucv])
if cct is None: if cct is None:
cct = lucv cct = lucv
MakeUvFaceEqualRectangle(vertsDict, lucv, rucv, rdcv, ldcv, cct, square) MakeUvFaceEqualRectangle(vertsDict, lucv, rucv, rdcv, ldcv, cct, square)
return return
def MakeUvFaceEqualRectangle(vertsDict, lucv, rucv, rdcv, ldcv, startv, square=False):
def MakeUvFaceEqualRectangle(vertsDict, lucv, rucv, rdcv, ldcv, startv, square = False):
ratioX, ratioY = ImageRatio() ratioX, ratioY = ImageRatio()
ratio = ratioX/ratioY ratio = ratioX/ratioY
if startv is None: startv = lucv.uv if startv is None:
elif AreVertsQuasiEqual(startv, rucv): startv = rucv.uv startv = lucv.uv
elif AreVertsQuasiEqual(startv, rdcv): startv = rdcv.uv elif AreVertsQuasiEqual(startv, rucv):
elif AreVertsQuasiEqual(startv, ldcv): startv = ldcv.uv startv = rucv.uv
else: startv = lucv.uv elif AreVertsQuasiEqual(startv, rdcv):
startv = rdcv.uv
elif AreVertsQuasiEqual(startv, ldcv):
startv = ldcv.uv
else:
startv = lucv.uv
lucv = lucv.uv lucv = lucv.uv
rucv = rucv.uv rucv = rucv.uv
rdcv = rdcv.uv rdcv = rdcv.uv
ldcv = ldcv.uv ldcv = ldcv.uv
if (startv == lucv): if (startv == lucv):
finalScaleX = hypotVert(lucv, rucv) finalScaleX = hypotVert(lucv, rucv)
finalScaleY = hypotVert(lucv, ldcv) finalScaleY = hypotVert(lucv, ldcv)
currRowX = lucv.x currRowX = lucv.x
currRowY = lucv.y currRowY = lucv.y
elif (startv == rucv): elif (startv == rucv):
finalScaleX = hypotVert(rucv, lucv) finalScaleX = hypotVert(rucv, lucv)
finalScaleY = hypotVert(rucv, rdcv) finalScaleY = hypotVert(rucv, rdcv)
currRowX = rucv.x - finalScaleX currRowX = rucv.x - finalScaleX
currRowY = rucv.y currRowY = rucv.y
elif (startv == rdcv): elif (startv == rdcv):
finalScaleX = hypotVert(rdcv, ldcv) finalScaleX = hypotVert(rdcv, ldcv)
finalScaleY = hypotVert(rdcv, rucv) finalScaleY = hypotVert(rdcv, rucv)
currRowX = rdcv.x - finalScaleX currRowX = rdcv.x - finalScaleX
currRowY = rdcv.y + finalScaleY currRowY = rdcv.y + finalScaleY
else: else:
finalScaleX = hypotVert(ldcv, rdcv) finalScaleX = hypotVert(ldcv, rdcv)
finalScaleY = hypotVert(ldcv, lucv) finalScaleY = hypotVert(ldcv, lucv)
currRowX = ldcv.x currRowX = ldcv.x
currRowY = ldcv.y +finalScaleY currRowY = ldcv.y + finalScaleY
if square: finalScaleY = finalScaleX*ratio if square:
finalScaleY = finalScaleX*ratio
#lucv, rucv #lucv, rucv
x = round(lucv.x, precision) x = round(lucv.x, precision)
y = round(lucv.y, precision) y = round(lucv.y, precision)
for v in vertsDict[(x,y)]: for v in vertsDict[(x, y)]:
v.uv.x = currRowX v.uv.x = currRowX
v.uv.y = currRowY v.uv.y = currRowY
x = round(rucv.x, precision) x = round(rucv.x, precision)
y = round(rucv.y, 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.x = currRowX + finalScaleX
v.uv.y = currRowY v.uv.y = currRowY
#rdcv, ldcv #rdcv, ldcv
x = round(rdcv.x, precision) x = round(rdcv.x, precision)
y = round(rdcv.y, precision) y = round(rdcv.y, precision)
for v in vertsDict[(x,y)]: for v in vertsDict[(x, y)]:
v.uv.x = currRowX + finalScaleX v.uv.x = currRowX + finalScaleX
v.uv.y = currRowY - finalScaleY v.uv.y = currRowY - finalScaleY
x = round(ldcv.x, precision) x = round(ldcv.x, precision)
y = round(ldcv.y, precision) y = round(ldcv.y, precision)
for v in vertsDict[(x,y)]: for v in vertsDict[(x, y)]:
v.uv.x = currRowX v.uv.x = currRowX
v.uv.y = currRowY - finalScaleY v.uv.y = currRowY - finalScaleY
return 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) bm = bmesh.from_edit_mesh(me)
uv_act = bm.loops.layers.uv.active uv_act = bm.loops.layers.uv.active
# our own local walker # our own local walker
def walk_face_init(faces, f_act): def walk_face_init(faces, f_act):
# first tag all faces True (so we dont uvmap them) # 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] l_b_uv = [l[uv_act].uv for l in l_b]
if EXTEND_MODE == 'LENGTH_AVERAGE': 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': elif EXTEND_MODE == 'LENGTH':
a0, b0, c0 = l_a[3].vert.co, l_a[0].vert.co, l_b[3].vert.co 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 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': if EXTEND_MODE == 'LENGTH_AVERAGE':
bm.edges.index_update() 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: for f in faces:
# we know its a quad # we know its a quad
l_quad = f.loops[:] l_quad = f.loops[:]
l_pair_a = (l_quad[0], l_quad[2]) l_pair_a = (l_quad[0], l_quad[2])
l_pair_b = (l_quad[1], l_quad[3]) 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_accum += e.calc_length()
edge_length_total += 1 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 # done with average length
# ------------------------ # ------------------------
@ -577,7 +575,7 @@ def FollowActiveUV(operator, me, f_act, faces, EXTEND_MODE = 'LENGTH_AVERAGE'):
def ImageRatio(): def ImageRatio():
ratioX, ratioY = 256,256 ratioX, ratioY = 256, 256
for a in bpy.context.screen.areas: for a in bpy.context.screen.areas:
if a.type == 'IMAGE_EDITOR': if a.type == 'IMAGE_EDITOR':
img = a.spaces[0].image img = a.spaces[0].image
@ -587,19 +585,18 @@ def ImageRatio():
return ratioX, ratioY return ratioX, ratioY
def Corners(corners): def Corners(corners):
firstHighest = corners[0] firstHighest = corners[0]
for c in corners: for c in corners:
if c.uv.y > firstHighest.uv.y: if c.uv.y > firstHighest.uv.y:
firstHighest = c firstHighest = c
corners.remove(firstHighest) corners.remove(firstHighest)
secondHighest = corners[0] secondHighest = corners[0]
for c in corners: for c in corners:
if (c.uv.y > secondHighest.uv.y): if (c.uv.y > secondHighest.uv.y):
secondHighest = c secondHighest = c
if firstHighest.uv.x < secondHighest.uv.x: if firstHighest.uv.x < secondHighest.uv.x:
leftUp = firstHighest leftUp = firstHighest
rightUp = secondHighest rightUp = secondHighest
@ -607,31 +604,29 @@ def Corners(corners):
leftUp = secondHighest leftUp = secondHighest
rightUp = firstHighest rightUp = firstHighest
corners.remove(secondHighest) corners.remove(secondHighest)
firstLowest = corners[0] firstLowest = corners[0]
secondLowest = corners[1] secondLowest = corners[1]
if firstLowest.uv.x < secondLowest.uv.x: if firstLowest.uv.x < secondLowest.uv.x:
leftDown = firstLowest leftDown = firstLowest
rightDown = secondLowest rightDown = secondLowest
else: else:
leftDown = secondLowest leftDown = secondLowest
rightDown = firstLowest rightDown = firstLowest
return leftUp, leftDown, rightUp, rightDown
return leftUp, leftDown, rightUp, rightDown
def AreVertsQuasiEqual(v1, v2, allowedError = 0.0009): 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: if abs(v1.uv.x - v2.uv.x) < allowedError and abs(v1.uv.y - v2.uv.y) < allowedError:
return True return True
return False return False
def hypotVert(v1, v2): def hypotVert(v1, v2):
hyp = hypot(v1.x - v2.x, v1.y - v2.y) hyp = hypot(v1.x - v2.x, v1.y - v2.y)
return hyp return hyp
bpy.utils.register_class(op)
bpy.utils.register_class(op)

@ -9,43 +9,43 @@ from collections import defaultdict
from . import utilities_texel from . import utilities_texel
from . import utilities_uv from . import utilities_uv
class op(bpy.types.Operator): class op(bpy.types.Operator):
bl_idname = "uv.textools_texel_density_set" bl_idname = "uv.textools_texel_density_set"
bl_label = "Set Texel size" bl_label = "Set Texel size"
bl_description = "Apply texel density by scaling the UV's to match the ratio" bl_description = "Apply texel density by scaling the UV's to match the ratio"
bl_options = {'REGISTER', 'UNDO'} bl_options = {'REGISTER', 'UNDO'}
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
if not bpy.context.active_object: if not bpy.context.active_object:
return False return False
if len(bpy.context.selected_objects) == 0: if len(bpy.context.selected_objects) == 0:
return False return False
if bpy.context.active_object.type != 'MESH': if bpy.context.active_object.type != 'MESH':
return False return False
#Only in UV editor mode # Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR': if bpy.context.area.type != 'IMAGE_EDITOR':
return False return False
#Requires UV map # Requires UV map
if not bpy.context.object.data.uv_layers: if not bpy.context.object.data.uv_layers:
return False return False
# if bpy.context.object.mode == 'EDIT': # if bpy.context.object.mode == 'EDIT':
# # In edit mode requires face select mode # # In edit mode requires face select mode
# if bpy.context.scene.tool_settings.mesh_select_mode[2] == False: # if bpy.context.scene.tool_settings.mesh_select_mode[2] == False:
# return False # return False
return True return True
def execute(self, context): def execute(self, context):
set_texel_density( set_texel_density(
self, self,
context, context,
bpy.context.scene.texToolsSettings.texel_mode_scale, bpy.context.scene.texToolsSettings.texel_mode_scale,
bpy.context.scene.texToolsSettings.texel_density bpy.context.scene.texToolsSettings.texel_density
@ -53,7 +53,6 @@ class op(bpy.types.Operator):
return {'FINISHED'} return {'FINISHED'}
def set_texel_density(self, context, mode, density): def set_texel_density(self, context, mode, density):
print("Set texel 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 is_sync = bpy.context.scene.tool_settings.use_uv_select_sync
object_faces = utilities_texel.get_selected_object_faces() object_faces = utilities_texel.get_selected_object_faces()
# Warning: No valid input objects # Warning: No valid input objects
if len(object_faces) == 0: 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 return
# Collect Images / textures # Collect Images / textures
@ -76,15 +74,15 @@ def set_texel_density(self, context, mode, density):
# Warning: No valid images # Warning: No valid images
if len(object_images) == 0: 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 return
for obj in object_faces: for obj in object_faces:
bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='DESELECT') bpy.ops.object.select_all(action='DESELECT')
bpy.context.view_layer.objects.active = obj 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 # Find image of object
image = object_images[obj] image = object_images[obj]
@ -118,35 +116,37 @@ def set_texel_density(self, context, mode, density):
print("group_faces {}x".format(len(group_faces))) print("group_faces {}x".format(len(group_faces)))
for group in group_faces: for group in group_faces:
# Get triangle areas # Get triangle areas
sum_area_vt = 0 sum_area_vt = 0
sum_area_uv = 0 sum_area_uv = 0
for face in group: for face in group:
# Triangle Verts # Triangle Verts
triangle_uv = [loop[uv_layers].uv for loop in face.loops ] triangle_uv = [loop[uv_layers].uv for loop in face.loops]
triangle_vt = [obj.matrix_world @ vert.co for vert in face.verts] triangle_vt = [obj.matrix_world @
vert.co for vert in face.verts]
#Triangle Areas # Triangle Areas
face_area_vt = utilities_texel.get_area_triangle( face_area_vt = utilities_texel.get_area_triangle(
triangle_vt[0], triangle_vt[0],
triangle_vt[1], triangle_vt[1],
triangle_vt[2] triangle_vt[2]
) )
face_area_uv = utilities_texel.get_area_triangle_uv( face_area_uv = utilities_texel.get_area_triangle_uv(
triangle_uv[0], triangle_uv[0],
triangle_uv[1], triangle_uv[1],
triangle_uv[2], triangle_uv[2],
image.size[0], image.size[0],
image.size[1] image.size[1]
) )
sum_area_vt+= math.sqrt( face_area_vt ) 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_uv += math.sqrt(face_area_uv) * \
min(image.size[0], image.size[1])
# Apply scale to group # 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 scale = 0
if density > 0 and sum_area_uv > 0 and sum_area_vt > 0: if density > 0 and sum_area_uv > 0 and sum_area_vt > 0:
scale = density / (sum_area_uv / sum_area_vt) 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 loop[uv_layers].select = True
print("Scale: {} {}x".format(scale, len(group))) 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 # Restore selection
utilities_uv.selection_restore() 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.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='DESELECT') bpy.ops.object.select_all(action='DESELECT')
for obj in object_faces: 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] bpy.context.view_layer.objects.active = list(object_faces.keys())[0]
# Restore edit mode # Restore edit mode
@ -186,4 +187,5 @@ def set_texel_density(self, context, mode, density):
if is_sync: if is_sync:
bpy.context.scene.tool_settings.use_uv_select_sync = True bpy.context.scene.tool_settings.use_uv_select_sync = True
bpy.utils.register_class(op) bpy.utils.register_class(op)

@ -2,7 +2,9 @@ import bpy
import bmesh import bmesh
import operator import operator
import math import math
import os, sys, subprocess import os
import sys
import subprocess
from . import settings from . import settings
from . import utilities_bake from . import utilities_bake
@ -13,27 +15,26 @@ class op(bpy.types.Operator):
bl_label = "Open Texture" bl_label = "Open Texture"
bl_description = "Open the texture on the system" bl_description = "Open the texture on the system"
name : bpy.props.StringProperty( name: bpy.props.StringProperty(
name="image name", name="image name",
default = "" default=""
) )
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
return True return True
def execute(self, context): def execute(self, context):
open_texture(self, context) open_texture(self, context)
return {'FINISHED'} return {'FINISHED'}
def open_texture(self, context): def open_texture(self, context):
print("Info") print("Info")
if self.name in bpy.data.images: if self.name in bpy.data.images:
image = bpy.data.images[self.name] image = bpy.data.images[self.name]
if image.filepath != "": if image.filepath != "":
path = bpy.path.abspath(image.filepath) path = bpy.path.abspath(image.filepath)
# https://meshlogic.github.io/posts/blender/addons/extra-image-list/ # https://meshlogic.github.io/posts/blender/addons/extra-image-list/
@ -43,8 +44,8 @@ def open_texture(self, context):
if sys.platform == "win32": if sys.platform == "win32":
os.startfile(path) os.startfile(path)
else: else:
opener ="open" if sys.platform == "darwin" else "xdg-open" opener = "open" if sys.platform == "darwin" else "xdg-open"
subprocess.call([opener, path]) subprocess.call([opener, path])
bpy.utils.register_class(op) bpy.utils.register_class(op)

@ -12,12 +12,12 @@ from . import utilities_bake
material_prefix = "TT_atlas_" material_prefix = "TT_atlas_"
gamma = 2.2 gamma = 2.2
class op(bpy.types.Operator): class op(bpy.types.Operator):
bl_idname = "uv.textools_texture_preview" bl_idname = "uv.textools_texture_preview"
bl_label = "Preview Texture" bl_label = "Preview Texture"
bl_description = "Preview the current UV image view background image on the selected object." bl_description = "Preview the current UV image view background image on the selected object."
bl_options = {'REGISTER', 'UNDO'} bl_options = {'REGISTER', 'UNDO'}
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
@ -26,25 +26,25 @@ class op(bpy.types.Operator):
if len(settings.sets) == 0: if len(settings.sets) == 0:
return False return False
# Only when we have a background image # Only when we have a background image
for area in bpy.context.screen.areas: for area in bpy.context.screen.areas:
if area.type == 'IMAGE_EDITOR': if area.type == 'IMAGE_EDITOR':
return area.spaces[0].image return area.spaces[0].image
return False return False
def execute(self, context): def execute(self, context):
print("PREVIEW TEXTURE????") print("PREVIEW TEXTURE????")
preview_texture(self, context) preview_texture(self, context)
return {'FINISHED'} return {'FINISHED'}
def preview_texture(self, context): def preview_texture(self, context):
# Collect all low objects from bake sets # 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 # Get view 3D area
view_area = None view_area = None
@ -57,9 +57,8 @@ def preview_texture(self, context):
# bpy.ops.view3d.localview({'area': view_area}) # bpy.ops.view3d.localview({'area': view_area})
# return # return
# Get background image # Get background image
image = None image = None
for area in bpy.context.screen.areas: for area in bpy.context.screen.areas:
if area.type == 'IMAGE_EDITOR': if area.type == 'IMAGE_EDITOR':
image = area.spaces[0].image image = area.spaces[0].image
@ -71,25 +70,25 @@ def preview_texture(self, context):
bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='DESELECT') 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.context.view_layer.objects.active = obj
for i in range(len(obj.material_slots)): for i in range(len(obj.material_slots)):
bpy.ops.object.material_slot_remove() bpy.ops.object.material_slot_remove()
#Create material with image # Create material with image
bpy.ops.object.material_slot_add() bpy.ops.object.material_slot_add()
obj.material_slots[0].material = utilities_bake.get_image_material(image) obj.material_slots[0].material = utilities_bake.get_image_material(
obj.display_type = 'TEXTURED' image)
obj.display_type = 'TEXTURED'
# Re-Select objects # Re-Select objects
bpy.ops.object.select_all(action='DESELECT') bpy.ops.object.select_all(action='DESELECT')
for obj in objects: for obj in objects:
obj.select_set( state = True, view_layer = None) obj.select_set(state=True, view_layer=None)
if view_area: if view_area:
#Change View mode to TEXTURED # Change View mode to TEXTURED
for space in view_area.spaces: for space in view_area.spaces:
if space.type == 'VIEW_3D': if space.type == 'VIEW_3D':
space.shading.type = 'MATERIAL' space.shading.type = 'MATERIAL'
@ -98,4 +97,5 @@ def preview_texture(self, context):
# bpy.ops.view3d.localview({'area': view_area}) # bpy.ops.view3d.localview({'area': view_area})
# bpy.ops.ui.textools_popup('INVOKE_DEFAULT', message="Object is in isolated view") # bpy.ops.ui.textools_popup('INVOKE_DEFAULT', message="Object is in isolated view")
bpy.utils.register_class(op)
bpy.utils.register_class(op)

@ -8,12 +8,13 @@ from math import pi
from . import utilities_uv from . import utilities_uv
from . import utilities_ui from . import utilities_ui
class op(bpy.types.Operator): class op(bpy.types.Operator):
bl_idname = "uv.textools_unwrap_edge_peel" bl_idname = "uv.textools_unwrap_edge_peel"
bl_label = "Peel Edge" bl_label = "Peel Edge"
bl_description = "Unwrap pipe along selected edges" bl_description = "Unwrap pipe along selected edges"
bl_options = {'REGISTER', 'UNDO'} bl_options = {'REGISTER', 'UNDO'}
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
@ -23,7 +24,7 @@ class op(bpy.types.Operator):
if bpy.context.active_object.type != 'MESH': if bpy.context.active_object.type != 'MESH':
return False return False
#Only in Edit mode # Only in Edit mode
if bpy.context.active_object.mode != 'EDIT': if bpy.context.active_object.mode != 'EDIT':
return False return False
@ -43,10 +44,10 @@ def unwrap_edges_pipe(self, context):
bm = bmesh.from_edit_mesh(bpy.context.active_object.data) bm = bmesh.from_edit_mesh(bpy.context.active_object.data)
uv_layers = bm.loops.layers.uv.verify() uv_layers = bm.loops.layers.uv.verify()
contextViewUV = utilities_ui.GetContextViewUV() contextViewUV = utilities_ui.GetContextViewUV()
if not contextViewUV: 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 return
# selected_initial = [edge for edge in bm.edges if edge.select] # 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] selected_edges = [edge for edge in bm.edges if edge.select]
if len(selected_edges) == 0: 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 return
# Convert linked selection to single UV island # 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] selected_faces = [face for face in bm.faces if face.select]
if len(selected_faces) == 0: if len(selected_faces) == 0:
self.report({'ERROR_INVALID_INPUT'}, "No faces available" ) self.report({'ERROR_INVALID_INPUT'}, "No faces available")
return return
# Mark previous selected edges as Seam # Mark previous selected edges as Seam
@ -92,4 +93,5 @@ def unwrap_edges_pipe(self, context):
# TODO: Restore initial selection # TODO: Restore initial selection
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')
bpy.utils.register_class(op)
bpy.utils.register_class(op)

@ -8,59 +8,61 @@ from math import pi
from . import utilities_uv from . import utilities_uv
from . import utilities_ui from . import utilities_ui
class op(bpy.types.Operator): class op(bpy.types.Operator):
bl_idname = "uv.textools_uv_crop" bl_idname = "uv.textools_uv_crop"
bl_label = "Crop" bl_label = "Crop"
bl_description = "Crop UV area to selected UV faces" bl_description = "Crop UV area to selected UV faces"
bl_options = {'REGISTER', 'UNDO'} bl_options = {'REGISTER', 'UNDO'}
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
if not bpy.context.active_object: if not bpy.context.active_object:
return False return False
if bpy.context.active_object.type != 'MESH': if bpy.context.active_object.type != 'MESH':
return False return False
#Only in Edit mode # Only in Edit mode
if bpy.context.active_object.mode != 'EDIT': if bpy.context.active_object.mode != 'EDIT':
return False return False
#Only in UV editor mode # Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR': if bpy.context.area.type != 'IMAGE_EDITOR':
return False return False
#Requires UV map # Requires UV map
if not bpy.context.object.data.uv_layers: if not bpy.context.object.data.uv_layers:
return False return False
return True return True
def execute(self, context): def execute(self, context):
crop(self, context) crop(self, context)
return {'FINISHED'} return {'FINISHED'}
def crop(self, context): def crop(self, context):
bm = bmesh.from_edit_mesh(bpy.context.active_object.data); bm = bmesh.from_edit_mesh(bpy.context.active_object.data)
uv_layers = bm.loops.layers.uv.verify(); uv_layers = bm.loops.layers.uv.verify()
padding = utilities_ui.get_padding() padding = utilities_ui.get_padding()
# Scale to fit bounds # Scale to fit bounds
bbox = utilities_uv.getSelectionBBox() bbox = utilities_uv.getSelectionBBox()
scale_u = (1.0-padding) / bbox['width'] scale_u = (1.0-padding) / bbox['width']
scale_v = (1.0-padding) / bbox['height'] scale_v = (1.0-padding) / bbox['height']
scale = min(scale_u, scale_v) 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 # Reposition
bbox = utilities_uv.getSelectionBBox() 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.ops.transform.translate(value=(delta_position.x, delta_position.y, 0))
bpy.utils.register_class(op)
bpy.utils.register_class(op)

@ -18,11 +18,11 @@ utilities_ui.icon_register("op_extend_canvas_BL_active.png")
utilities_ui.icon_register("op_extend_canvas_BR_active.png") utilities_ui.icon_register("op_extend_canvas_BR_active.png")
def on_dropdown_size_x(self, context): def on_dropdown_size_x(self, context):
self.size_x = int(self.dropdown_size_x) self.size_x = int(self.dropdown_size_x)
# context.area.tag_redraw() # context.area.tag_redraw()
def on_dropdown_size_y(self, context): def on_dropdown_size_y(self, context):
self.size_y = int(self.dropdown_size_y) self.size_y = int(self.dropdown_size_y)
# context.area.tag_redraw() # context.area.tag_redraw()
@ -34,63 +34,66 @@ class op(bpy.types.Operator):
bl_description = "Resize or extend the UV area" bl_description = "Resize or extend the UV area"
bl_options = {'REGISTER', 'UNDO'} bl_options = {'REGISTER', 'UNDO'}
size_x : bpy.props.IntProperty( size_x: bpy.props.IntProperty(
name = "Width", name="Width",
description="padding size in pixels", description="padding size in pixels",
default = 1024, default=1024,
min = 1, min=1,
max = 8192 max=8192
) )
size_y : bpy.props.IntProperty( size_y: bpy.props.IntProperty(
name = "Height", name="Height",
description="padding size in pixels", description="padding size in pixels",
default = 1024, default=1024,
min = 1, min=1,
max = 8192 max=8192
) )
dropdown_size_x : bpy.props.EnumProperty( dropdown_size_x: bpy.props.EnumProperty(
items = utilities_ui.size_textures, items=utilities_ui.size_textures,
name = "", name="",
update = on_dropdown_size_x, update=on_dropdown_size_x,
default = '1024' default='1024'
) )
dropdown_size_y : bpy.props.EnumProperty( dropdown_size_y: bpy.props.EnumProperty(
items = utilities_ui.size_textures, items=utilities_ui.size_textures,
name = "", name="",
update = on_dropdown_size_y, update=on_dropdown_size_y,
default = '1024' default='1024'
) )
direction : bpy.props.EnumProperty(name='direction', items=( direction: bpy.props.EnumProperty(name='direction', items=(
('TL',' ','Top Left', utilities_ui.icon_get("op_extend_canvas_TL_active"),0), ('TL', ' ', 'Top Left', utilities_ui.icon_get(
('BL',' ','Bottom Left', utilities_ui.icon_get("op_extend_canvas_BL_active"),2), "op_extend_canvas_TL_active"), 0),
('TR',' ','Top Right', utilities_ui.icon_get("op_extend_canvas_TR_active"),1), ('BL', ' ', 'Bottom Left', utilities_ui.icon_get(
('BR',' ','Bottom Right', utilities_ui.icon_get("op_extend_canvas_BR_active"),3) "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 @classmethod
def poll(cls, context): def poll(cls, context):
if not bpy.context.active_object: if not bpy.context.active_object:
return False return False
if bpy.context.active_object.type != 'MESH': if bpy.context.active_object.type != 'MESH':
return False return False
#Only in Edit mode # Only in Edit mode
if bpy.context.active_object.mode != 'EDIT': if bpy.context.active_object.mode != 'EDIT':
return False return False
#Only in UV editor mode # Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR': if bpy.context.area.type != 'IMAGE_EDITOR':
return False return False
#Requires UV map # Requires UV map
if not bpy.context.object.data.uv_layers: if not bpy.context.object.data.uv_layers:
return False return False
return True return True
def invoke(self, context, event): def invoke(self, context, event):
print("Invoke resize area") print("Invoke resize area")
self.size_x = bpy.context.scene.texToolsSettings.size[0] self.size_x = bpy.context.scene.texToolsSettings.size[0]
@ -105,8 +108,7 @@ class op(bpy.types.Operator):
self.dropdown_size_y = item[0] self.dropdown_size_y = item[0]
break 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): def check(self, context):
return True 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/ # https://b3d.interplanety.org/en/creating-pop-up-panels-with-user-ui-in-blender-add-on/
layout = self.layout layout = self.layout
layout.separator() layout.separator()
# New Size # New Size
row = layout.row() row = layout.row()
split = row.split(factor=0.6) split = row.split(factor=0.6)
c = split.column(align=True) c = split.column(align=True)
c.prop(self, "size_x", text="X",expand=True) c.prop(self, "size_x", text="X", expand=True)
c.prop(self, "size_y", text="Y",expand=True) c.prop(self, "size_y", text="Y", expand=True)
c = split.column(align=True) c = split.column(align=True)
c.prop(self, "dropdown_size_x", text="") c.prop(self, "dropdown_size_x", text="")
@ -133,10 +134,11 @@ class op(bpy.types.Operator):
col = layout.column(align=True) col = layout.column(align=True)
col.label(text="Direction") col.label(text="Direction")
row = col.row(align=True) row = col.row(align=True)
row.prop(self,'direction', expand=True) row.prop(self, 'direction', expand=True)
# Summary # 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]: if bpy.context.scene.texToolsSettings.size[0] == bpy.context.scene.texToolsSettings.size[1]:
size_A = "{}²".format(bpy.context.scene.texToolsSettings.size[0]) size_A = "{}²".format(bpy.context.scene.texToolsSettings.size[0])
size_B = "{} x {}".format(self.size_x, self.size_y) size_B = "{} x {}".format(self.size_x, self.size_y)
@ -147,21 +149,19 @@ class op(bpy.types.Operator):
size_A, size_B size_A, size_B
)) ))
layout.separator() layout.separator()
def execute(self, context): def execute(self, context):
#Store selection # Store selection
utilities_uv.selection_store() utilities_uv.selection_store()
# Get start and end size # Get start and end size
size_A = Vector([ size_A = Vector([
bpy.context.scene.texToolsSettings.size[0], bpy.context.scene.texToolsSettings.size[0],
bpy.context.scene.texToolsSettings.size[1] bpy.context.scene.texToolsSettings.size[1]
]) ])
size_B = Vector([ size_B = Vector([
self.size_x, self.size_x,
self.size_y self.size_y
]) ])
@ -170,7 +170,7 @@ class op(bpy.types.Operator):
self, self,
context, context,
self.direction, self.direction,
size_A, size_A,
size_B size_B
) )
resize_image( 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[0] = self.size_x
bpy.context.scene.texToolsSettings.size[1] = self.size_y bpy.context.scene.texToolsSettings.size[1] = self.size_y
#Restore selection # Restore selection
utilities_uv.selection_restore() utilities_uv.selection_restore()
return {'FINISHED'} return {'FINISHED'}
def resize_uv(self, context, mode, size_A, size_B): def resize_uv(self, context, mode, size_A, size_B):
# Set pivot # Set pivot
bpy.context.tool_settings.transform_pivot_point = 'CURSOR' bpy.context.tool_settings.transform_pivot_point = 'CURSOR'
if mode == 'TL': if mode == 'TL':
bpy.ops.uv.cursor_set(location=Vector([0,1])) bpy.ops.uv.cursor_set(location=Vector([0, 1]))
elif mode == 'TR': elif mode == 'TR':
bpy.ops.uv.cursor_set(location=Vector([1,1])) bpy.ops.uv.cursor_set(location=Vector([1, 1]))
elif mode == 'BL': elif mode == 'BL':
bpy.ops.uv.cursor_set(location=Vector([0,0])) bpy.ops.uv.cursor_set(location=Vector([0, 0]))
elif mode == 'BR': 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 # Select all UV faces
bpy.ops.uv.select_all(action='SELECT') bpy.ops.uv.select_all(action='SELECT')
@ -209,24 +208,25 @@ def resize_uv(self, context, mode, size_A, size_B):
# Resize # Resize
scale_x = size_A.x / size_B.x scale_x = size_A.x / size_B.x
scale_y = size_A.y / size_B.y 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): 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 # 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 # 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 != None:
if context.area.spaces.active.image != None: if context.area.spaces.active.image != None:
image = context.area.spaces.active.image 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: if name_texture in image.name or image == image_obj:
# Resize Image UV editor background image # 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: else:
# No Image assigned # No Image assigned
@ -234,20 +234,21 @@ def resize_image(context, mode, size_A, size_B):
# Get background color from theme + 1.25x brighter # Get background color from theme + 1.25x brighter
theme = bpy.context.preferences.themes[0] theme = bpy.context.preferences.themes[0]
color = theme.image_editor.space.back.copy() color = theme.image_editor.space.back.copy()
color.r*= 1.15 color.r *= 1.15
color.g*= 1.15 color.g *= 1.15
color.b*= 1.15 color.b *= 1.15
image = None image = None
if name_texture in bpy.data.images: if name_texture in bpy.data.images:
# TexTools Image already exists # TexTools Image already exists
image = bpy.data.images[name_texture] 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_width = int(size_B.x)
image.generated_height = int(size_B.y) image.generated_height = int(size_B.y)
else: else:
# Create new image # 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_color = (color.r, color.g, color.b, 1.0)
image.generated_type = 'BLANK' image.generated_type = 'BLANK'
image.generated_width = int(size_B.x) 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() utilities_texel.checker_images_cleanup()
bpy.utils.register_class(op)
bpy.utils.register_class(op)

@ -12,27 +12,26 @@ from . import utilities_color
# from . import op_bake # from . import op_bake
keywords_low = ['lowpoly','low','lowp','lp','lo','l'] keywords_low = ['lowpoly', 'low', 'lowp', 'lp', 'lo', 'l']
keywords_high = ['highpoly','high','highp','hp','hi','h'] keywords_high = ['highpoly', 'high', 'highp', 'hp', 'hi', 'h']
keywords_cage = ['cage','c'] keywords_cage = ['cage', 'c']
keywords_float = ['floater','float','f'] keywords_float = ['floater', 'float', 'f']
split_chars = [' ','_','.','-']
split_chars = [' ', '_', '.', '-']
class BakeMode: class BakeMode:
material = "" #Material name from external blend file material = "" # Material name from external blend file
type = 'EMIT' type = 'EMIT'
normal_space = 'TANGENT' normal_space = 'TANGENT'
setVColor = None #Set Vertex color method setVColor = None # Set Vertex color method
color = (0.23, 0.23, 0.23, 1) #Background color color = (0.23, 0.23, 0.23, 1) # Background color
engine = 'CYCLES' #render engine, by default CYCLES engine = 'CYCLES' # render engine, by default CYCLES
composite = None #use composite scene to process end result composite = None # use composite scene to process end result
use_project = False #Bake projected? use_project = False # Bake projected?
params = [] #UI Parameters from scene settings 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): 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.material = material
self.type = type self.type = type
self.normal_space = normal_space self.normal_space = normal_space
@ -44,7 +43,6 @@ class BakeMode:
self.use_project = use_project self.use_project = use_project
def on_select_bake_mode(mode): def on_select_bake_mode(mode):
print("Mode changed {}".format(mode)) print("Mode changed {}".format(mode))
@ -81,8 +79,6 @@ def store_bake_settings():
settings.bake_objects_hide_render = [] settings.bake_objects_hide_render = []
# for obj in bpy.context.view_layer.objects: # for obj in bpy.context.view_layer.objects:
# if obj.hide_render == False and obj not in objects_sets: # if obj.hide_render == False and obj not in objects_sets:
# Check if layer is active: # Check if layer is active:
@ -96,7 +92,6 @@ def store_bake_settings():
# obj.cycles_visibility.shadow = False # obj.cycles_visibility.shadow = False
def restore_bake_settings(): def restore_bake_settings():
# Render Settings # Render Settings
if settings.bake_render_engine != '': if settings.bake_render_engine != '':
@ -111,59 +106,59 @@ def restore_bake_settings():
# obj.cycles_visibility.shadow = True # obj.cycles_visibility.shadow = True
stored_materials = {} stored_materials = {}
stored_material_faces = {} stored_material_faces = {}
def store_materials_clear(): def store_materials_clear():
stored_materials.clear() stored_materials.clear()
stored_material_faces.clear() stored_material_faces.clear()
def store_materials(obj): def store_materials(obj):
stored_materials[obj] = [] stored_materials[obj] = []
stored_material_faces[obj] = [] stored_material_faces[obj] = []
# Enter edit mode # Enter edit mode
bpy.ops.object.select_all(action='DESELECT') 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.context.view_layer.objects.active = obj
bpy.ops.object.mode_set(mode='EDIT') 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)): for s in range(len(obj.material_slots)):
slot = obj.material_slots[s] slot = obj.material_slots[s]
stored_materials[obj].append(slot.material) 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]) )) # print("Faces: {}x".format( len(stored_material_faces[obj][-1]) ))
if slot and slot.material: if slot and slot.material:
slot.material.name = "backup_"+slot.material.name 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 # Back to object mode
bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.mode_set(mode='OBJECT')
def restore_materials(): def restore_materials():
for obj in stored_materials: for obj in stored_materials:
# Enter edit mode # Enter edit mode
bpy.context.view_layer.objects.active = obj bpy.context.view_layer.objects.active = obj
bpy.ops.object.mode_set(mode='EDIT') bpy.ops.object.mode_set(mode='EDIT')
bm = bmesh.from_edit_mesh(obj.data); bm = bmesh.from_edit_mesh(obj.data)
# Restore slots # Restore slots
for index in range(len(stored_materials[obj])): for index in range(len(stored_materials[obj])):
material = stored_materials[obj][index] material = stored_materials[obj][index]
faces = stored_material_faces[obj][index] faces = stored_material_faces[obj][index]
if material: if material:
material.name = material.name.replace("backup_","") material.name = material.name.replace("backup_", "")
obj.material_slots[index].material = material obj.material_slots[index].material = material
# Face material indexies # Face material indexies
@ -180,12 +175,11 @@ def restore_materials():
bpy.ops.object.material_slot_remove() bpy.ops.object.material_slot_remove()
def get_set_name_base(obj): def get_set_name_base(obj):
def remove_digits(name): def remove_digits(name):
# Remove blender naming digits, e.g. cube.001, cube.002,... # 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[:-4]
return name return name
@ -202,7 +196,6 @@ def get_set_name_base(obj):
return remove_digits(obj.name).lower() return remove_digits(obj.name).lower()
def get_set_name(obj): def get_set_name(obj):
# Get Basic name # Get Basic name
name = get_set_name_base(obj) name = get_set_name_base(obj)
@ -210,7 +203,7 @@ def get_set_name(obj):
# Split by ' ','_','.' etc. # Split by ' ','_','.' etc.
split = name.lower() split = name.lower()
for char in split_chars: for char in split_chars:
split = split.replace(char,' ') split = split.replace(char, ' ')
strings = split.split(' ') strings = split.split(' ')
# Remove all keys from name # Remove all keys from name
@ -231,7 +224,6 @@ def get_set_name(obj):
return "_".join(new_strings) return "_".join(new_strings)
def get_object_type(obj): def get_object_type(obj):
name = get_set_name_base(obj) name = get_set_name_base(obj)
@ -239,11 +231,11 @@ def get_object_type(obj):
# Detect by name pattern # Detect by name pattern
split = name.lower() split = name.lower()
for char in split_chars: for char in split_chars:
split = split.replace(char,' ') split = split.replace(char, ' ')
strings = split.split(' ') strings = split.split(' ')
# Detect float, more rare than low # Detect float, more rare than low
for string in strings: for string in strings:
for key in keywords_float: for key in keywords_float:
if key == string: if key == string:
return 'float' return 'float'
@ -257,33 +249,28 @@ def get_object_type(obj):
elif modifier.type == 'BEVEL': elif modifier.type == 'BEVEL':
return 'high' return 'high'
# Detect High first, more rare # Detect High first, more rare
for string in strings: for string in strings:
for key in keywords_high: for key in keywords_high:
if key == string: if key == string:
return 'high' return 'high'
# Detect cage, more rare than low # Detect cage, more rare than low
for string in strings: for string in strings:
for key in keywords_cage: for key in keywords_cage:
if key == string: if key == string:
return 'cage' return 'cage'
# Detect low # Detect low
for string in strings: for string in strings:
for key in keywords_low: for key in keywords_low:
if key == string: if key == string:
return 'low' return 'low'
# if nothing was detected, assume its low # if nothing was detected, assume its low
return 'low' return 'low'
def get_baked_images(sets): def get_baked_images(sets):
images = [] images = []
for set in sets: for set in sets:
@ -295,19 +282,18 @@ def get_baked_images(sets):
return images return images
def get_bake_sets(): def get_bake_sets():
filtered = {} filtered = {}
for obj in bpy.context.selected_objects: for obj in bpy.context.selected_objects:
if obj.type == 'MESH': if obj.type == 'MESH':
filtered[obj] = get_object_type(obj) filtered[obj] = get_object_type(obj)
groups = [] groups = []
# Group by names # Group by names
for obj in filtered: for obj in filtered:
name = get_set_name(obj) name = get_set_name(obj)
if len(groups)==0: if len(groups) == 0:
groups.append([obj]) groups.append([obj])
else: else:
isFound = False isFound = False
@ -330,10 +316,9 @@ def get_bake_sets():
if key == get_set_name(group[0]): if key == get_set_name(group[0]):
sorted_groups.append(group) sorted_groups.append(group)
break break
groups = sorted_groups
# print("Keys: "+", ".join(keys))
groups = sorted_groups
# print("Keys: "+", ".join(keys))
bake_sets = [] bake_sets = []
for group in groups: for group in groups:
@ -351,19 +336,17 @@ def get_bake_sets():
elif filtered[obj] == 'float': elif filtered[obj] == 'float':
float.append(obj) float.append(obj)
name = get_set_name(group[0]) name = get_set_name(group[0])
bake_sets.append(BakeSet(name, low, cage, high, float)) bake_sets.append(BakeSet(name, low, cage, high, float))
return bake_sets return bake_sets
class BakeSet: class BakeSet:
objects_low = [] #low poly geometry objects_low = [] # low poly geometry
objects_cage = [] #Cage low poly geometry objects_cage = [] # Cage low poly geometry
objects_high = [] #High poly geometry objects_high = [] # High poly geometry
objects_float = [] #Floating geometry objects_float = [] # Floating geometry
name = "" name = ""
has_issues = False has_issues = False
@ -390,14 +373,12 @@ class BakeSet:
break break
def setup_vertex_color_selection(obj): def setup_vertex_color_selection(obj):
bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='DESELECT') 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.context.view_layer.objects.active = obj
bpy.ops.object.mode_set(mode='VERTEX_PAINT') 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') bpy.ops.object.mode_set(mode='OBJECT')
def setup_vertex_color_dirty(obj): def setup_vertex_color_dirty(obj):
print("setup_vertex_color_dirty {}".format(obj.name)) print("setup_vertex_color_dirty {}".format(obj.name))
bpy.ops.object.select_all(action='DESELECT') 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.context.view_layer.objects.active = obj
bpy.ops.object.mode_set(mode='EDIT') bpy.ops.object.mode_set(mode='EDIT')
# Fill white then, # Fill white then,
bm = bmesh.from_edit_mesh(obj.data) bm = bmesh.from_edit_mesh(obj.data)
colorLayer = bm.loops.layers.color.verify() 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 face in bm.faces:
for loop in face.loops: for loop in face.loops:
loop[colorLayer] = color loop[colorLayer] = color
obj.data.update() obj.data.update()
# Back to object mode # Back to object mode
@ -443,13 +422,11 @@ def setup_vertex_color_dirty(obj):
bpy.ops.paint.vertex_color_dirt() bpy.ops.paint.vertex_color_dirt()
def setup_vertex_color_id_material(obj): def setup_vertex_color_id_material(obj):
bpy.ops.object.select_all(action='DESELECT') 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.context.view_layer.objects.active = obj
bpy.ops.object.mode_set(mode='EDIT') bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE') bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE')
@ -482,10 +459,9 @@ def setup_vertex_color_id_material(obj):
bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.mode_set(mode='OBJECT')
def setup_vertex_color_id_element(obj): def setup_vertex_color_id_element(obj):
bpy.ops.object.select_all(action='DESELECT') 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.context.view_layer.objects.active = obj
bpy.ops.object.mode_set(mode='EDIT') bpy.ops.object.mode_set(mode='EDIT')
@ -510,9 +486,9 @@ def setup_vertex_color_id_element(obj):
groups.append(linked) groups.append(linked)
# Color each group # 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.get_color_id(i, len(groups))
color = utilities_color.safe_color( color ) color = utilities_color.safe_color(color)
for face in groups[i]: for face in groups[i]:
for loop in face.loops: for loop in face.loops:
loop[colorLayer] = color loop[colorLayer] = color
@ -535,7 +511,6 @@ def get_image_material(image):
else: else:
material = bpy.data.materials.new(image.name) material = bpy.data.materials.new(image.name)
# Cyles Material # Cyles Material
if bpy.context.scene.render.engine == 'CYCLES' or bpy.context.scene.render.engine == 'BLENDER_EEVEE': if bpy.context.scene.render.engine == 'CYCLES' or bpy.context.scene.render.engine == 'BLENDER_EEVEE':
material.use_nodes = True material.use_nodes = True
@ -551,17 +526,17 @@ def get_image_material(image):
node_image.image = image node_image.image = image
material.node_tree.nodes.active = node_image material.node_tree.nodes.active = node_image
#Base Diffuse BSDF # Base Diffuse BSDF
node_diffuse = material.node_tree.nodes['Principled BSDF'] node_diffuse = material.node_tree.nodes['Principled BSDF']
if "_normal_" in image.name: if "_normal_" in image.name:
# Add Normal Map Nodes # Add Normal Map Nodes
node_normal_map = None node_normal_map = None
if "normal_map" in material.node_tree.nodes: if "normal_map" in material.node_tree.nodes:
node_normal_map = material.node_tree.nodes["normal_map"] node_normal_map = material.node_tree.nodes["normal_map"]
else: 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" node_normal_map.name = "normal_map"
# Tangent or World space # Tangent or World space
@ -571,10 +546,12 @@ def get_image_material(image):
node_normal_map.space = 'WORLD' node_normal_map.space = 'WORLD'
# image to normal_map link # 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 # 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_normal_map.location = node_diffuse.location - Vector((200, 0))
node_image.location = node_normal_map.location - Vector((200, 0)) node_image.location = node_normal_map.location - Vector((200, 0))
@ -582,15 +559,16 @@ def get_image_material(image):
else: else:
# Other images display as Color # Other images display as Color
# dump(node_image.color_mapping.bl_rna.property_tags) # dump(node_image.color_mapping.bl_rna.property_tags)
# image node to diffuse node link # 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 return material
elif bpy.context.scene.render.engine == 'BLENDER_EEVEE': elif bpy.context.scene.render.engine == 'BLENDER_EEVEE':
material.use_nodes = True material.use_nodes = True
texture = None texture = None
if image.name in bpy.data.textures: if image.name in bpy.data.textures:
texture = bpy.data.textures[image.name] texture = bpy.data.textures[image.name]
@ -600,6 +578,6 @@ def get_image_material(image):
texture.image = image texture.image = image
slot = material.texture_slot.add() slot = material.texture_slot.add()
slot.texture = texture slot.texture = texture
slot.mapping = 'FLAT' slot.mapping = 'FLAT'
# return material # return material

@ -43,28 +43,26 @@ def assign_color(index):
material = get_material(index) material = get_material(index)
if material: if material:
# material.use_nodes = False # material.use_nodes = False
rgb = get_color(index) rgb = get_color(index)
rgba = (rgb[0], rgb[1], rgb[2], 1) 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) # Cycles material (Preferred for baking)
material.node_tree.nodes["Principled BSDF"].inputs[0].default_value = rgba material.node_tree.nodes["Principled BSDF"].inputs[0].default_value = rgba
material.diffuse_color = rgba material.diffuse_color = rgba
elif not material.use_nodes and bpy.context.scene.render.engine == 'BLENDER_EEVEE': elif not material.use_nodes and bpy.context.scene.render.engine == 'BLENDER_EEVEE':
# Legacy render engine, not suited for baking # Legacy render engine, not suited for baking
material.diffuse_color = rgba material.diffuse_color = rgba
def get_material(index): def get_material(index):
name = get_name(index) name = get_name(index)
# Material already exists? # Material already exists?
if name in bpy.data.materials: if name in bpy.data.materials:
material = bpy.data.materials[name]; material = bpy.data.materials[name]
# Check for incorrect matreials for current render engine # Check for incorrect matreials for current render engine
if not material: if not material:
@ -77,7 +75,7 @@ def get_material(index):
replace_material(index) replace_material(index)
else: else:
return material; return material
print("Could nt find {} , create a new one??".format(name)) print("Could nt find {} , create a new one??".format(name))
@ -86,7 +84,6 @@ def get_material(index):
return material return material
# Replaace an existing material with a new one # Replaace an existing material with a new one
# This is sometimes necessary after switching the render engine # This is sometimes necessary after switching the render engine
def replace_material(index): def replace_material(index):
@ -96,11 +93,11 @@ def replace_material(index):
# Check if material exists # Check if material exists
if name in bpy.data.materials: if name in bpy.data.materials:
material = bpy.data.materials[name]; material = bpy.data.materials[name]
# Collect material slots we have to re-assign # Collect material slots we have to re-assign
slots = [] slots = []
for obj in bpy.context.view_layer.objects: for obj in bpy.context.view_layer.objects:
for slot in obj.material_slots: for slot in obj.material_slots:
if slot.material == material: if slot.material == material:
slots.append(slot) slots.append(slot)
@ -108,12 +105,11 @@ def replace_material(index):
# Get new material # Get new material
material.user_clear() material.user_clear()
bpy.data.materials.remove(material) bpy.data.materials.remove(material)
# Re-assign new material to all previous slots # Re-assign new material to all previous slots
material = create_material(index) material = create_material(index)
for slot in slots: for slot in slots:
slot.material = material; slot.material = material
def create_material(index): def create_material(index):
@ -125,17 +121,15 @@ def create_material(index):
if bpy.context.scene.render.engine == 'CYCLES': if bpy.context.scene.render.engine == 'CYCLES':
# Cycles: prefer nodes as it simplifies baking # Cycles: prefer nodes as it simplifies baking
material.use_nodes = True material.use_nodes = True
return material return material
def get_name(index): def get_name(index):
return (material_prefix+"{:02d}").format(index) return (material_prefix+"{:02d}").format(index)
def get_color(index): def get_color(index):
if index < bpy.context.scene.texToolsSettings.color_ID_count: if index < bpy.context.scene.texToolsSettings.color_ID_count:
return getattr(bpy.context.scene.texToolsSettings, "color_ID_color_{}".format(index)) return getattr(bpy.context.scene.texToolsSettings, "color_ID_color_{}".format(index))
@ -144,16 +138,15 @@ def get_color(index):
return (0, 0, 0) return (0, 0, 0)
def set_color(index, color): def set_color(index, color):
if index < bpy.context.scene.texToolsSettings.color_ID_count: 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): def validate_face_colors(obj):
# Validate face colors and material slots # 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 count = bpy.context.scene.texToolsSettings.color_ID_count
# Verify enough material slots # Verify enough material slots
@ -165,12 +158,11 @@ def validate_face_colors(obj):
else: else:
break break
# TODO: Check face.material_index # TODO: Check face.material_index
bpy.ops.object.mode_set(mode='EDIT') 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: for face in bm.faces:
face.material_index%= count face.material_index %= count
obj.data.update() obj.data.update()
# Remove material slots that are not used # 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): for i in range(len(obj.material_slots) - count):
if len(obj.material_slots) > count: if len(obj.material_slots) > count:
# Remove last # 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() bpy.ops.object.material_slot_remove()
# Restore previous mode # Restore previous mode
bpy.ops.object.mode_set(mode=previous_mode) bpy.ops.object.mode_set(mode=previous_mode)
def hex_to_color(hex): def hex_to_color(hex):
hex = hex.strip('#') hex = hex.strip('#')
lv = len(hex) lv = len(hex)
fin = list(int(hex[i:i + lv // 3], 16) for i in range(0, lv, lv // 3)) 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) return tuple(fin)
def color_to_hex(color): def color_to_hex(color):
rgb = [] rgb = []
for i in range(3): 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) r = int(rgb[0]*255)
g = int(rgb[1]*255) g = int(rgb[1]*255)
b = int(rgb[2]*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): def get_color_id(index, count):
# Get unique color # Get unique color
color = Color() color = Color()
color.hsv = ( index / (count) ), 0.9, 1.0 color.hsv = (index / (count)), 0.9, 1.0
return color return color

@ -11,20 +11,21 @@ from . import op_bake
preview_collections = {} preview_collections = {}
size_textures = [ size_textures = [
('32', '32', ''), ('32', '32', ''),
('64', '64', ''), ('64', '64', ''),
('128', '128', ''), ('128', '128', ''),
('256', '256', ''), ('256', '256', ''),
('512', '512', ''), ('512', '512', ''),
('1024', '1024', ''), ('1024', '1024', ''),
('2048', '2048', ''), ('2048', '2048', ''),
('4096', '4096', ''), ('4096', '4096', ''),
('8192', '8192', '') ('8192', '8192', '')
] ]
preview_icons = bpy.utils.previews.new() preview_icons = bpy.utils.previews.new()
def icon_get(name): def icon_get(name):
return preview_icons[name].icon_id return preview_icons[name].icon_id
@ -33,11 +34,12 @@ def GetContextView3D():
for window in bpy.context.window_manager.windows: for window in bpy.context.window_manager.windows:
screen = window.screen screen = window.screen
for area in screen.areas: for area in screen.areas:
if area.type == 'VIEW_3D': if area.type == 'VIEW_3D':
for region in area.regions: for region in area.regions:
if region.type == 'WINDOW': 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! override = {'window': window, 'screen': screen, 'area': area, 'region': region, 'scene': bpy.context.scene, 'edit_object': bpy.context.edit_object,
return override '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 return None
@ -45,86 +47,80 @@ def GetContextViewUV():
for window in bpy.context.window_manager.windows: for window in bpy.context.window_manager.windows:
screen = window.screen screen = window.screen
for area in screen.areas: for area in screen.areas:
if area.type == 'IMAGE_EDITOR': if area.type == 'IMAGE_EDITOR':
for region in area.regions: for region in area.regions:
if region.type == 'WINDOW': 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! override = {'window': window, 'screen': screen, 'area': area, 'region': region, 'scene': bpy.context.scene, 'edit_object': bpy.context.edit_object,
return override '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 return None
def icon_register(fileName): def icon_register(fileName):
name = fileName.split('.')[0] # Don't include file extension name = fileName.split('.')[0] # Don't include file extension
icons_dir = os.path.join(os.path.dirname(__file__), "icons") icons_dir = os.path.join(os.path.dirname(__file__), "icons")
preview_icons.load(name, os.path.join(icons_dir, fileName), 'IMAGE') preview_icons.load(name, os.path.join(icons_dir, fileName), 'IMAGE')
def get_padding(): 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 return bpy.context.scene.texToolsSettings.padding / size_min
def generate_bake_mode_previews(): def generate_bake_mode_previews():
# We are accessing all of the information that we generated in the register function below # We are accessing all of the information that we generated in the register function below
preview_collection = preview_collections["thumbnail_previews"] preview_collection = preview_collections["thumbnail_previews"]
image_location = preview_collection.images_location image_location = preview_collection.images_location
VALID_EXTENSIONS = ('.png', '.jpg', '.jpeg') VALID_EXTENSIONS = ('.png', '.jpg', '.jpeg')
enum_items = [] enum_items = []
# Generate the thumbnails # Generate the thumbnails
for i, image in enumerate(os.listdir(image_location)): for i, image in enumerate(os.listdir(image_location)):
mode = image[0:-4] mode = image[0:-4]
print(".. .{}".format(mode)) print(".. .{}".format(mode))
if image.endswith(VALID_EXTENSIONS) and mode in op_bake.modes: if image.endswith(VALID_EXTENSIONS) and mode in op_bake.modes:
filepath = os.path.join(image_location, image) filepath = os.path.join(image_location, image)
thumb = preview_collection.load(filepath, filepath, 'IMAGE') thumb = preview_collection.load(filepath, filepath, 'IMAGE')
enum_items.append((image, mode, "", thumb.icon_id, i)) enum_items.append((image, mode, "", thumb.icon_id, i))
return enum_items return enum_items
def get_bake_mode(): 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): class op_popup(bpy.types.Operator):
bl_idname = "ui.textools_popup" bl_idname = "ui.textools_popup"
bl_label = "Message" bl_label = "Message"
message : StringProperty() message: StringProperty()
def execute(self, context): def execute(self, context):
self.report({'INFO'}, self.message) self.report({'INFO'}, self.message)
print(self.message) print(self.message)
return {'FINISHED'} return {'FINISHED'}
def invoke(self, context, event): def invoke(self, context, event):
wm = context.window_manager wm = context.window_manager
return wm.invoke_popup(self, width=200, height=200) return wm.invoke_popup(self, width=200, height=200)
def draw(self, context): def draw(self, context):
self.layout.label(text=self.message) self.layout.label(text=self.message)
def on_bakemode_set(self, context): def on_bakemode_set(self, context):
print("Set '{}'".format(bpy.context.scene.TT_bake_mode)) print("Set '{}'".format(bpy.context.scene.TT_bake_mode))
utilities_bake.on_select_bake_mode(get_bake_mode()) utilities_bake.on_select_bake_mode(get_bake_mode())
def register(): def register():
from bpy.types import Scene from bpy.types import Scene
from bpy.props import StringProperty, EnumProperty from bpy.props import StringProperty, EnumProperty
print("_______REgister previews") print("_______REgister previews")
# Operators # Operators
@ -135,19 +131,19 @@ def register():
# Create a new preview collection (only upon register) # Create a new preview collection (only upon register)
preview_collection = bpy.utils.previews.new() 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 preview_collections["thumbnail_previews"] = preview_collection
# This is an EnumProperty to hold all of the images # 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 # You really can save it anywhere in bpy.types.* Just make sure the location makes sense
bpy.types.Scene.TT_bake_mode = EnumProperty( bpy.types.Scene.TT_bake_mode = EnumProperty(
items=generate_bake_mode_previews(), items=generate_bake_mode_previews(),
update = on_bakemode_set, update=on_bakemode_set,
default = 'normal_tangent.png' default='normal_tangent.png'
) )
def unregister(): def unregister():
print("_______UNregister previews") print("_______UNregister previews")
@ -156,15 +152,14 @@ def unregister():
for preview_collection in preview_collections.values(): for preview_collection in preview_collections.values():
bpy.utils.previews.remove(preview_collection) bpy.utils.previews.remove(preview_collection)
preview_collections.clear() preview_collections.clear()
# Unregister icons # Unregister icons
# global preview_icons # global preview_icons
bpy.utils.previews.remove(preview_icons) bpy.utils.previews.remove(preview_icons)
del bpy.types.Scene.TT_bake_mode del bpy.types.Scene.TT_bake_mode
if __name__ == "__main__": if __name__ == "__main__":
register() register()
bpy.utils.register_class(op_popup) bpy.utils.register_class(op_popup)

Loading…
Cancel
Save