Tabs to Spaces

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

File diff suppressed because it is too large Load Diff

@ -9,119 +9,119 @@ from . import utilities_uv
class op(bpy.types.Operator):
bl_idname = "uv.textools_align"
bl_label = "Align"
bl_description = "Align vertices, edges or shells"
bl_options = {'REGISTER', 'UNDO'}
direction : bpy.props.StringProperty(name="Direction", default="top")
bl_idname = "uv.textools_align"
bl_label = "Align"
bl_description = "Align vertices, edges or shells"
bl_options = {'REGISTER', 'UNDO'}
direction : bpy.props.StringProperty(name="Direction", default="top")
@classmethod
def poll(cls, context):
if not bpy.context.active_object:
return False
@classmethod
def poll(cls, context):
if not bpy.context.active_object:
return False
#Only in Edit mode
if bpy.context.active_object.mode != 'EDIT':
return False
#Only in Edit mode
if bpy.context.active_object.mode != 'EDIT':
return False
#Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR':
return False
#Requires UV map
if not bpy.context.object.data.uv_layers:
return False #self.report({'WARNING'}, "Object must have more than one UV map")
#Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR':
return False
#Requires UV map
if not bpy.context.object.data.uv_layers:
return False #self.report({'WARNING'}, "Object must have more than one UV map")
# Not in Synced mode
if bpy.context.scene.tool_settings.use_uv_select_sync:
return False
# Not in Synced mode
if bpy.context.scene.tool_settings.use_uv_select_sync:
return False
return True
return True
def execute(self, context):
align(context, self.direction)
return {'FINISHED'}
def execute(self, context):
align(context, self.direction)
return {'FINISHED'}
def align(context, direction):
#Store selection
utilities_uv.selection_store()
if bpy.context.tool_settings.transform_pivot_point != 'CURSOR':
bpy.context.tool_settings.transform_pivot_point = 'CURSOR'
#B-Mesh
obj = bpy.context.active_object
bm = bmesh.from_edit_mesh(obj.data);
uv_layers = bm.loops.layers.uv.verify();
if len(obj.data.uv_layers) == 0:
print("There is no UV channel or UV data set")
return
# Collect BBox sizes
boundsAll = utilities_uv.getSelectionBBox()
mode = bpy.context.scene.tool_settings.uv_select_mode
if mode == 'FACE' or mode == 'ISLAND':
print("____ Align Islands")
#Collect UV islands
islands = utilities_uv.getSelectionIslands()
for island in islands:
bpy.ops.uv.select_all(action='DESELECT')
utilities_uv.set_selected_faces(island)
bounds = utilities_uv.getSelectionBBox()
# print("Island "+str(len(island))+"x faces, delta: "+str(delta.y))
if direction == "bottom":
delta = boundsAll['min'] - bounds['min']
bpy.ops.transform.translate(value=(0, delta.y, 0))
elif direction == "top":
delta = boundsAll['max'] - bounds['max']
bpy.ops.transform.translate(value=(0, delta.y, 0))
elif direction == "left":
delta = boundsAll['min'] - bounds['min']
bpy.ops.transform.translate(value=(delta.x, 0, 0))
elif direction == "right":
delta = boundsAll['max'] - bounds['max']
bpy.ops.transform.translate(value=(delta.x, 0, 0))
else:
print("Unkown direction: "+str(direction))
elif mode == 'EDGE' or mode == 'VERTEX':
print("____ Align Verts")
for f in bm.faces:
if f.select:
for l in f.loops:
luv = l[uv_layers]
if luv.select:
# print("Idx: "+str(luv.uv))
if direction == "top":
luv.uv[1] = boundsAll['max'].y
elif direction == "bottom":
luv.uv[1] = boundsAll['min'].y
elif direction == "left":
luv.uv[0] = boundsAll['min'].x
elif direction == "right":
luv.uv[0] = boundsAll['max'].x
bmesh.update_edit_mesh(obj.data)
#Restore selection
utilities_uv.selection_restore()
#Store selection
utilities_uv.selection_store()
if bpy.context.tool_settings.transform_pivot_point != 'CURSOR':
bpy.context.tool_settings.transform_pivot_point = 'CURSOR'
#B-Mesh
obj = bpy.context.active_object
bm = bmesh.from_edit_mesh(obj.data);
uv_layers = bm.loops.layers.uv.verify();
if len(obj.data.uv_layers) == 0:
print("There is no UV channel or UV data set")
return
# Collect BBox sizes
boundsAll = utilities_uv.getSelectionBBox()
mode = bpy.context.scene.tool_settings.uv_select_mode
if mode == 'FACE' or mode == 'ISLAND':
print("____ Align Islands")
#Collect UV islands
islands = utilities_uv.getSelectionIslands()
for island in islands:
bpy.ops.uv.select_all(action='DESELECT')
utilities_uv.set_selected_faces(island)
bounds = utilities_uv.getSelectionBBox()
# print("Island "+str(len(island))+"x faces, delta: "+str(delta.y))
if direction == "bottom":
delta = boundsAll['min'] - bounds['min']
bpy.ops.transform.translate(value=(0, delta.y, 0))
elif direction == "top":
delta = boundsAll['max'] - bounds['max']
bpy.ops.transform.translate(value=(0, delta.y, 0))
elif direction == "left":
delta = boundsAll['min'] - bounds['min']
bpy.ops.transform.translate(value=(delta.x, 0, 0))
elif direction == "right":
delta = boundsAll['max'] - bounds['max']
bpy.ops.transform.translate(value=(delta.x, 0, 0))
else:
print("Unkown direction: "+str(direction))
elif mode == 'EDGE' or mode == 'VERTEX':
print("____ Align Verts")
for f in bm.faces:
if f.select:
for l in f.loops:
luv = l[uv_layers]
if luv.select:
# print("Idx: "+str(luv.uv))
if direction == "top":
luv.uv[1] = boundsAll['max'].y
elif direction == "bottom":
luv.uv[1] = boundsAll['min'].y
elif direction == "left":
luv.uv[0] = boundsAll['min'].x
elif direction == "right":
luv.uv[0] = boundsAll['max'].x
bmesh.update_edit_mesh(obj.data)
#Restore selection
utilities_uv.selection_restore()
bpy.utils.register_class(op)

File diff suppressed because it is too large Load Diff

@ -11,211 +11,211 @@ frame_range = 50
class op(bpy.types.Operator):
bl_idname = "uv.textools_bake_explode"
bl_label = "Explode"
bl_description = "Explode selected bake pairs with animation keyframes"
bl_options = {'REGISTER', 'UNDO'}
bl_idname = "uv.textools_bake_explode"
bl_label = "Explode"
bl_description = "Explode selected bake pairs with animation keyframes"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
if len(settings.sets) <= 1:
return False
@classmethod
def poll(cls, context):
if len(settings.sets) <= 1:
return False
return True
return True
def execute(self, context):
explode(self)
def execute(self, context):
explode(self)
return {'FINISHED'}
return {'FINISHED'}
def explode(self):
sets = settings.sets
sets = settings.sets
set_bounds = {}
set_volume = {}
avg_side = 0
for set in sets:
set_bounds[set] = get_bbox_set(set)
set_volume[set] = set_bounds[set]['size'].x * set_bounds[set]['size'].y * set_bounds[set]['size'].z
set_bounds = {}
set_volume = {}
avg_side = 0
for set in sets:
set_bounds[set] = get_bbox_set(set)
set_volume[set] = set_bounds[set]['size'].x * set_bounds[set]['size'].y * set_bounds[set]['size'].z
avg_side+=set_bounds[set]['size'].x
avg_side+=set_bounds[set]['size'].y
avg_side+=set_bounds[set]['size'].z
avg_side+=set_bounds[set]['size'].x
avg_side+=set_bounds[set]['size'].y
avg_side+=set_bounds[set]['size'].z
avg_side/=(len(sets)*3)
avg_side/=(len(sets)*3)
sorted_set_volume = sorted(set_volume.items(), key=operator.itemgetter(1))
sorted_sets = [item[0] for item in sorted_set_volume]
sorted_sets.reverse()
sorted_set_volume = sorted(set_volume.items(), key=operator.itemgetter(1))
sorted_sets = [item[0] for item in sorted_set_volume]
sorted_sets.reverse()
# All combined bounding boxes
bbox_all = merge_bounds(list(set_bounds.values()))
bbox_max = set_bounds[ sorted_sets[0] ] # max_bbox(list(set_bounds.values()))
# All combined bounding boxes
bbox_all = merge_bounds(list(set_bounds.values()))
bbox_max = set_bounds[ sorted_sets[0] ] # max_bbox(list(set_bounds.values()))
# Offset sets into their direction
dir_offset_last_bbox = {}
for i in range(0,6):
dir_offset_last_bbox[i] = bbox_max #bbox_all
# Offset sets into their direction
dir_offset_last_bbox = {}
for i in range(0,6):
dir_offset_last_bbox[i] = bbox_max #bbox_all
bpy.context.scene.frame_start = 0
bpy.context.scene.frame_end = frame_range
bpy.context.scene.frame_current = 0
bpy.context.scene.frame_start = 0
bpy.context.scene.frame_end = frame_range
bpy.context.scene.frame_current = 0
# Process each set
for set in sorted_sets:
if set_bounds[set] != bbox_max:
delta = set_bounds[set]['center'] - bbox_all['center']
offset_set(set, delta, avg_side*0.35, dir_offset_last_bbox )
# Process each set
for set in sorted_sets:
if set_bounds[set] != bbox_max:
delta = set_bounds[set]['center'] - bbox_all['center']
offset_set(set, delta, avg_side*0.35, dir_offset_last_bbox )
def offset_set(set, delta, margin, dir_offset_last_bbox):
objects = set.objects_low + set.objects_high + set.objects_cage
# print("\nSet '{}' with {}x".format(set.name, len(objects) ))
# Which Direction?
delta_max = max(abs(delta.x), abs(delta.y), abs(delta.z))
direction = [0,0,0]
if delta_max > 0:
for i in range(0,3):
if abs(delta[i]) == delta_max:
direction[i] = delta[i]/abs(delta[i])
else:
direction[i] = 0
else:
# Default when not delta offset was measure move up
direction = [0,0,1]
delta = Vector((direction[0], direction[1], direction[2]))
# Get Key
key = get_delta_key(delta)
# Calculate Offset
bbox = get_bbox_set(set)
bbox_last = dir_offset_last_bbox[key]
offset = Vector((0,0,0))
if delta.x == 1:
offset = delta * ( bbox_last['max'].x - bbox['min'].x )
elif delta.x == -1:
offset = delta * -( bbox_last['min'].x - bbox['max'].x )
elif delta.y == 1:
offset = delta * ( bbox_last['max'].y - bbox['min'].y )
elif delta.y == -1:
offset = delta * -( bbox_last['min'].y - bbox['max'].y )
elif delta.z == 1:
offset = delta * ( bbox_last['max'].z - bbox['min'].z )
elif delta.z == -1:
offset = delta * -( bbox_last['min'].z - bbox['max'].z )
# Add margin
offset+= delta * margin
# Offset items
# https://blenderartists.org/forum/showthread.php?237761-Blender-2-6-Set-keyframes-using-Python-script
# http://blenderscripting.blogspot.com.au/2011/05/inspired-by-post-on-ba-it-just-so.html
# Set key A
bpy.context.scene.frame_current = 0
for obj in objects:
obj.keyframe_insert(data_path="location")
for obj in objects:
obj.location += offset
bpy.context.view_layer.update()
# Set key B
bpy.context.scene.frame_current = frame_range
for obj in objects:
obj.keyframe_insert(data_path="location")
# Update last bbox in direction
dir_offset_last_bbox[key] = get_bbox_set(set)
objects = set.objects_low + set.objects_high + set.objects_cage
# print("\nSet '{}' with {}x".format(set.name, len(objects) ))
# Which Direction?
delta_max = max(abs(delta.x), abs(delta.y), abs(delta.z))
direction = [0,0,0]
if delta_max > 0:
for i in range(0,3):
if abs(delta[i]) == delta_max:
direction[i] = delta[i]/abs(delta[i])
else:
direction[i] = 0
else:
# Default when not delta offset was measure move up
direction = [0,0,1]
delta = Vector((direction[0], direction[1], direction[2]))
# Get Key
key = get_delta_key(delta)
# Calculate Offset
bbox = get_bbox_set(set)
bbox_last = dir_offset_last_bbox[key]
offset = Vector((0,0,0))
if delta.x == 1:
offset = delta * ( bbox_last['max'].x - bbox['min'].x )
elif delta.x == -1:
offset = delta * -( bbox_last['min'].x - bbox['max'].x )
elif delta.y == 1:
offset = delta * ( bbox_last['max'].y - bbox['min'].y )
elif delta.y == -1:
offset = delta * -( bbox_last['min'].y - bbox['max'].y )
elif delta.z == 1:
offset = delta * ( bbox_last['max'].z - bbox['min'].z )
elif delta.z == -1:
offset = delta * -( bbox_last['min'].z - bbox['max'].z )
# Add margin
offset+= delta * margin
# Offset items
# https://blenderartists.org/forum/showthread.php?237761-Blender-2-6-Set-keyframes-using-Python-script
# http://blenderscripting.blogspot.com.au/2011/05/inspired-by-post-on-ba-it-just-so.html
# Set key A
bpy.context.scene.frame_current = 0
for obj in objects:
obj.keyframe_insert(data_path="location")
for obj in objects:
obj.location += offset
bpy.context.view_layer.update()
# Set key B
bpy.context.scene.frame_current = frame_range
for obj in objects:
obj.keyframe_insert(data_path="location")
# Update last bbox in direction
dir_offset_last_bbox[key] = get_bbox_set(set)
def get_delta_key(delta):
# print("Get key {} is: {}".format(delta, delta.y == -1 ))
if delta.x == -1:
return 0
elif delta.x == 1:
return 1
if delta.y == -1:
return 2
elif delta.y == 1:
return 3
if delta.z == -1:
return 4
elif delta.z == 1:
return 5
# print("Get key {} is: {}".format(delta, delta.y == -1 ))
if delta.x == -1:
return 0
elif delta.x == 1:
return 1
if delta.y == -1:
return 2
elif delta.y == 1:
return 3
if delta.z == -1:
return 4
elif delta.z == 1:
return 5
def merge_bounds(bounds):
box_min = bounds[0]['min'].copy()
box_max = bounds[0]['max'].copy()
for bbox in bounds:
# box_min.x = -8
box_min.x = min(box_min.x, bbox['min'].x)
box_min.y = min(box_min.y, bbox['min'].y)
box_min.z = min(box_min.z, bbox['min'].z)
box_max.x = max(box_max.x, bbox['max'].x)
box_max.y = max(box_max.y, bbox['max'].y)
box_max.z = max(box_max.z, bbox['max'].z)
return {
'min':box_min,
'max':box_max,
'size':(box_max-box_min),
'center':box_min+(box_max-box_min)/2
}
box_min = bounds[0]['min'].copy()
box_max = bounds[0]['max'].copy()
for bbox in bounds:
# box_min.x = -8
box_min.x = min(box_min.x, bbox['min'].x)
box_min.y = min(box_min.y, bbox['min'].y)
box_min.z = min(box_min.z, bbox['min'].z)
box_max.x = max(box_max.x, bbox['max'].x)
box_max.y = max(box_max.y, bbox['max'].y)
box_max.z = max(box_max.z, bbox['max'].z)
return {
'min':box_min,
'max':box_max,
'size':(box_max-box_min),
'center':box_min+(box_max-box_min)/2
}
def get_bbox_set(set):
objects = set.objects_low + set.objects_high + set.objects_cage
bounds = []
for obj in objects:
bounds.append( get_bbox(obj) )
return merge_bounds(bounds)
objects = set.objects_low + set.objects_high + set.objects_cage
bounds = []
for obj in objects:
bounds.append( get_bbox(obj) )
return merge_bounds(bounds)
def get_bbox(obj):
corners = [obj.matrix_world @ Vector(corner) for corner in obj.bound_box]
# Get world space Min / Max
box_min = Vector((corners[0].x, corners[0].y, corners[0].z))
box_max = Vector((corners[0].x, corners[0].y, corners[0].z))
for corner in corners:
# box_min.x = -8
box_min.x = min(box_min.x, corner.x)
box_min.y = min(box_min.y, corner.y)
box_min.z = min(box_min.z, corner.z)
box_max.x = max(box_max.x, corner.x)
box_max.y = max(box_max.y, corner.y)
box_max.z = max(box_max.z, corner.z)
return {
'min':box_min,
'max':box_max,
'size':(box_max-box_min),
'center':box_min+(box_max-box_min)/2
}
corners = [obj.matrix_world @ Vector(corner) for corner in obj.bound_box]
# Get world space Min / Max
box_min = Vector((corners[0].x, corners[0].y, corners[0].z))
box_max = Vector((corners[0].x, corners[0].y, corners[0].z))
for corner in corners:
# box_min.x = -8
box_min.x = min(box_min.x, corner.x)
box_min.y = min(box_min.y, corner.y)
box_min.z = min(box_min.z, corner.z)
box_max.x = max(box_max.x, corner.x)
box_max.y = max(box_max.y, corner.y)
box_max.z = max(box_max.z, corner.z)
return {
'min':box_min,
'max':box_max,
'size':(box_max-box_min),
'center':box_min+(box_max-box_min)/2
}
bpy.utils.register_class(op)

@ -10,170 +10,170 @@ from . import utilities_bake
class op(bpy.types.Operator):
bl_idname = "uv.textools_bake_organize_names"
bl_label = "Match Names"
bl_description = "Match high poly object names to low poly objects by their bounding boxes."
bl_options = {'REGISTER', 'UNDO'}
bl_idname = "uv.textools_bake_organize_names"
bl_label = "Match Names"
bl_description = "Match high poly object names to low poly objects by their bounding boxes."
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
# Require 2 or more objects to sort
if len(bpy.context.selected_objects) <= 1:
return False
@classmethod
def poll(cls, context):
# Require 2 or more objects to sort
if len(bpy.context.selected_objects) <= 1:
return False
return True
return True
def execute(self, context):
sort_objects(self)
return {'FINISHED'}
def execute(self, context):
sort_objects(self)
return {'FINISHED'}
def sort_objects(self):
# Collect objects
objects = []
bounds = {}
for obj in bpy.context.selected_objects:
if obj.type == 'MESH':
objects.append(obj)
bounds[obj] = get_bbox(obj)
print("Objects {}x".format(len(objects)))
# Get smallest side of any bounding box
min_side = min(bounds[objects[0]]['size'].x, bounds[objects[0]]['size'].y, bounds[objects[0]]['size'].z)
avg_side = 0
for obj in bounds:
min_side = min(min_side, bounds[obj]['size'].x, bounds[obj]['size'].y, bounds[obj]['size'].z)
avg_side+=bounds[obj]['size'].x
avg_side+=bounds[obj]['size'].y
avg_side+=bounds[obj]['size'].z
avg_side/=(len(bounds)*3)
# Get all Low and high poly objects
objects_low = [obj for obj in objects if utilities_bake.get_object_type(obj)=='low']
objects_high = [obj for obj in objects if utilities_bake.get_object_type(obj)=='high']
if len(objects_low) == 0:
self.report({'ERROR_INVALID_INPUT'}, "There are no low poly objects selected")
return
elif len(objects_high) == 0:
self.report({'ERROR_INVALID_INPUT'}, "There are no high poly objects selected")
return
print("Low {}x, High {}x".format(len(objects_low),len(objects_high)))
pairs_low_high = {}
objects_left_high = objects_high.copy()
for obj_A in objects_low:
matches = {}
for obj_B in objects_left_high:
score = get_score(obj_A, obj_B)
p = score / avg_side
if p > 0 and p <= 0.65:
matches[obj_B] = p
else:
print("Not matched: {} ".format(p))
if(len(matches) > 0):
sorted_matches = sorted(matches.items(), key=operator.itemgetter(1))
for i in range(0, len(sorted_matches)):
A = obj_A
B = sorted_matches[i][0]
p = sorted_matches[i][1]
print("Check: {}% '{}' = '{}' ".format(int(p * 100.0), A.name, B.name ))
# Remove from list
objects_left_high.remove(sorted_matches[0][0])
pairs_low_high[obj_A] = sorted_matches[0][0]
print("")
# objects_unsorted = [obj for obj in objects if (obj not in pairs_low_high.values() and obj not in pairs_low_high.keys() )]
bpy.ops.object.select_all(action='DESELECT')
for obj_A in pairs_low_high:
obj_B = pairs_low_high[obj_A]
try:
obj_B.name = utilities_bake.get_bake_name(obj_A)+" high"
obj_A.select_set( state = True, view_layer = None)
obj_B.select_set( state = True, view_layer = None)
except:
print("Fail")
print("Matched {}x".format(len(pairs_low_high)))
# Collect objects
objects = []
bounds = {}
for obj in bpy.context.selected_objects:
if obj.type == 'MESH':
objects.append(obj)
bounds[obj] = get_bbox(obj)
print("Objects {}x".format(len(objects)))
# Get smallest side of any bounding box
min_side = min(bounds[objects[0]]['size'].x, bounds[objects[0]]['size'].y, bounds[objects[0]]['size'].z)
avg_side = 0
for obj in bounds:
min_side = min(min_side, bounds[obj]['size'].x, bounds[obj]['size'].y, bounds[obj]['size'].z)
avg_side+=bounds[obj]['size'].x
avg_side+=bounds[obj]['size'].y
avg_side+=bounds[obj]['size'].z
avg_side/=(len(bounds)*3)
# Get all Low and high poly objects
objects_low = [obj for obj in objects if utilities_bake.get_object_type(obj)=='low']
objects_high = [obj for obj in objects if utilities_bake.get_object_type(obj)=='high']
if len(objects_low) == 0:
self.report({'ERROR_INVALID_INPUT'}, "There are no low poly objects selected")
return
elif len(objects_high) == 0:
self.report({'ERROR_INVALID_INPUT'}, "There are no high poly objects selected")
return
print("Low {}x, High {}x".format(len(objects_low),len(objects_high)))
pairs_low_high = {}
objects_left_high = objects_high.copy()
for obj_A in objects_low:
matches = {}
for obj_B in objects_left_high:
score = get_score(obj_A, obj_B)
p = score / avg_side
if p > 0 and p <= 0.65:
matches[obj_B] = p
else:
print("Not matched: {} ".format(p))
if(len(matches) > 0):
sorted_matches = sorted(matches.items(), key=operator.itemgetter(1))
for i in range(0, len(sorted_matches)):
A = obj_A
B = sorted_matches[i][0]
p = sorted_matches[i][1]
print("Check: {}% '{}' = '{}' ".format(int(p * 100.0), A.name, B.name ))
# Remove from list
objects_left_high.remove(sorted_matches[0][0])
pairs_low_high[obj_A] = sorted_matches[0][0]
print("")
# objects_unsorted = [obj for obj in objects if (obj not in pairs_low_high.values() and obj not in pairs_low_high.keys() )]
bpy.ops.object.select_all(action='DESELECT')
for obj_A in pairs_low_high:
obj_B = pairs_low_high[obj_A]
try:
obj_B.name = utilities_bake.get_bake_name(obj_A)+" high"
obj_A.select_set( state = True, view_layer = None)
obj_B.select_set( state = True, view_layer = None)
except:
print("Fail")
print("Matched {}x".format(len(pairs_low_high)))
def get_score(A, B):
bbox_A = get_bbox(A)
bbox_B = get_bbox(B)
bbox_A = get_bbox(A)
bbox_B = get_bbox(B)
# Not colliding
if not is_colliding(bbox_A, bbox_B):
return -1.0
# Not colliding
if not is_colliding(bbox_A, bbox_B):
return -1.0
# Position
delta_pos = (bbox_B['center'] - bbox_A['center']).length
# Position
delta_pos = (bbox_B['center'] - bbox_A['center']).length
# Volume
volume_A = bbox_A['size'].x * bbox_A['size'].y * bbox_A['size'].z
volume_B = bbox_B['size'].x * bbox_B['size'].y * bbox_B['size'].z
delta_vol = (max(volume_A, volume_B) - min(volume_A, volume_B))/3.0
# Volume
volume_A = bbox_A['size'].x * bbox_A['size'].y * bbox_A['size'].z
volume_B = bbox_B['size'].x * bbox_B['size'].y * bbox_B['size'].z
delta_vol = (max(volume_A, volume_B) - min(volume_A, volume_B))/3.0
# Longest side
side_A_max = max(bbox_A['size'].x, bbox_A['size'].y, bbox_A['size'].z )
side_B_max = max(bbox_B['size'].x, bbox_B['size'].y, bbox_B['size'].z )
delta_size_max = abs(side_A_max - side_B_max)
# Longest side
side_A_max = max(bbox_A['size'].x, bbox_A['size'].y, bbox_A['size'].z )
side_B_max = max(bbox_B['size'].x, bbox_B['size'].y, bbox_B['size'].z )
delta_size_max = abs(side_A_max - side_B_max)
return delta_pos + delta_vol + delta_size_max
return delta_pos + delta_vol + delta_size_max
def get_bbox(obj):
corners = [obj.matrix_world @ Vector(corner) for corner in obj.bound_box]
# Get world space Min / Max
box_min = Vector((corners[0].x, corners[0].y, corners[0].z))
box_max = Vector((corners[0].x, corners[0].y, corners[0].z))
for corner in corners:
# box_min.x = -8
box_min.x = min(box_min.x, corner.x)
box_min.y = min(box_min.y, corner.y)
box_min.z = min(box_min.z, corner.z)
box_max.x = max(box_max.x, corner.x)
box_max.y = max(box_max.y, corner.y)
box_max.z = max(box_max.z, corner.z)
return {
'min':box_min,
'max':box_max,
'size':(box_max-box_min),
'center':box_min+(box_max-box_min)/2
}
corners = [obj.matrix_world @ Vector(corner) for corner in obj.bound_box]
# Get world space Min / Max
box_min = Vector((corners[0].x, corners[0].y, corners[0].z))
box_max = Vector((corners[0].x, corners[0].y, corners[0].z))
for corner in corners:
# box_min.x = -8
box_min.x = min(box_min.x, corner.x)
box_min.y = min(box_min.y, corner.y)
box_min.z = min(box_min.z, corner.z)
box_max.x = max(box_max.x, corner.x)
box_max.y = max(box_max.y, corner.y)
box_max.z = max(box_max.z, corner.z)
return {
'min':box_min,
'max':box_max,
'size':(box_max-box_min),
'center':box_min+(box_max-box_min)/2
}
def is_colliding(bbox_A, bbox_B):
def is_collide_1D(A_min, A_max, B_min, B_max):
# One line is inside the other
length_A = A_max-A_min
length_B = B_max-B_min
center_A = A_min + length_A/2
center_B = B_min + length_B/2
def is_collide_1D(A_min, A_max, B_min, B_max):
# One line is inside the other
length_A = A_max-A_min
length_B = B_max-B_min
center_A = A_min + length_A/2
center_B = B_min + length_B/2
return abs(center_A - center_B) <= (length_A+length_B)/2
return abs(center_A - center_B) <= (length_A+length_B)/2
collide_x = is_collide_1D(bbox_A['min'].x, bbox_A['max'].x, bbox_B['min'].x, bbox_B['max'].x)
collide_y = is_collide_1D(bbox_A['min'].y, bbox_A['max'].y, bbox_B['min'].y, bbox_B['max'].y)
collide_z = is_collide_1D(bbox_A['min'].z, bbox_A['max'].z, bbox_B['min'].z, bbox_B['max'].z)
collide_x = is_collide_1D(bbox_A['min'].x, bbox_A['max'].x, bbox_B['min'].x, bbox_B['max'].x)
collide_y = is_collide_1D(bbox_A['min'].y, bbox_A['max'].y, bbox_B['min'].y, bbox_B['max'].y)
collide_z = is_collide_1D(bbox_A['min'].z, bbox_A['max'].z, bbox_B['min'].z, bbox_B['max'].z)
return collide_x and collide_y and collide_z
return collide_x and collide_y and collide_z
bpy.utils.register_class(op)

@ -8,91 +8,91 @@ from math import pi
from . import utilities_color
class op(bpy.types.Operator):
bl_idname = "uv.textools_color_assign"
bl_label = "Assign Color"
bl_description = "Assign color to selected objects or faces in edit mode."
bl_options = {'REGISTER', 'UNDO'}
index : bpy.props.IntProperty(description="Color Index", default=0)
bl_idname = "uv.textools_color_assign"
bl_label = "Assign Color"
bl_description = "Assign color to selected objects or faces in edit mode."
bl_options = {'REGISTER', 'UNDO'}
index : bpy.props.IntProperty(description="Color Index", default=0)
@classmethod
def poll(cls, context):
if not bpy.context.active_object:
return False
@classmethod
def poll(cls, context):
if not bpy.context.active_object:
return False
if bpy.context.active_object not in bpy.context.selected_objects:
return False
if bpy.context.active_object not in bpy.context.selected_objects:
return False
if bpy.context.active_object.type != 'MESH':
return False
if bpy.context.active_object.type != 'MESH':
return False
#Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR':
return False
#Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR':
return False
return True
def execute(self, context):
assign_color(self, context, self.index)
return {'FINISHED'}
return True
def execute(self, context):
assign_color(self, context, self.index)
return {'FINISHED'}
def assign_color(self, context, index):
selected_obj = bpy.context.selected_objects.copy()
previous_mode = 'OBJECT'
if len(selected_obj) == 1:
previous_mode = bpy.context.active_object.mode
for obj in selected_obj:
# Select object
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='DESELECT')
obj.select_set( state = True, view_layer = None)
bpy.context.view_layer.objects.active = obj
# Enter Edit mode
bpy.ops.object.mode_set(mode='EDIT')
bm = bmesh.from_edit_mesh(obj.data);
faces = []
#Assign to all or just selected faces?
if previous_mode == 'EDIT':
faces = [face for face in bm.faces if face.select]
else:
faces = [face for face in bm.faces]
if previous_mode == 'OBJECT':
bpy.ops.mesh.select_all(action='SELECT')
# Verify material slots
for i in range(index+1):
if index >= len(obj.material_slots):
bpy.ops.object.material_slot_add()
utilities_color.assign_slot(obj, index)
# Assign to selection
obj.active_material_index = index
bpy.ops.object.material_slot_assign()
#Change View mode to MATERIAL
# for area in bpy.context.screen.areas:
# if area.type == 'VIEW_3D':
# for space in area.spaces:
# if space.type == 'VIEW_3D':
# space.shading.type = 'MATERIAL'
# restore mode
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='DESELECT')
for obj in selected_obj:
obj.select_set( state = True, view_layer = None)
bpy.ops.object.mode_set(mode=previous_mode)
bpy.utils.register_class(op)
selected_obj = bpy.context.selected_objects.copy()
previous_mode = 'OBJECT'
if len(selected_obj) == 1:
previous_mode = bpy.context.active_object.mode
for obj in selected_obj:
# Select object
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='DESELECT')
obj.select_set( state = True, view_layer = None)
bpy.context.view_layer.objects.active = obj
# Enter Edit mode
bpy.ops.object.mode_set(mode='EDIT')
bm = bmesh.from_edit_mesh(obj.data);
faces = []
#Assign to all or just selected faces?
if previous_mode == 'EDIT':
faces = [face for face in bm.faces if face.select]
else:
faces = [face for face in bm.faces]
if previous_mode == 'OBJECT':
bpy.ops.mesh.select_all(action='SELECT')
# Verify material slots
for i in range(index+1):
if index >= len(obj.material_slots):
bpy.ops.object.material_slot_add()
utilities_color.assign_slot(obj, index)
# Assign to selection
obj.active_material_index = index
bpy.ops.object.material_slot_assign()
#Change View mode to MATERIAL
# for area in bpy.context.screen.areas:
# if area.type == 'VIEW_3D':
# for space in area.spaces:
# if space.type == 'VIEW_3D':
# space.shading.type = 'MATERIAL'
# restore mode
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='DESELECT')
for obj in selected_obj:
obj.select_set( state = True, view_layer = None)
bpy.ops.object.mode_set(mode=previous_mode)
bpy.utils.register_class(op)

@ -8,89 +8,89 @@ from math import pi
from . import utilities_color
class op(bpy.types.Operator):
bl_idname = "uv.textools_color_clear"
bl_label = "Clear Colors"
bl_description = "Clears the Color IDs and materials on the selected model"
bl_options = {'REGISTER', 'UNDO'}
bl_idname = "uv.textools_color_clear"
bl_label = "Clear Colors"
bl_description = "Clears the Color IDs and materials on the selected model"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
if not bpy.context.active_object:
return False
@classmethod
def poll(cls, context):
if not bpy.context.active_object:
return False
if bpy.context.active_object not in bpy.context.selected_objects:
return False
if bpy.context.active_object not in bpy.context.selected_objects:
return False
if bpy.context.active_object.type != 'MESH':
return False
if bpy.context.active_object.type != 'MESH':
return False
#Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR':
return False
#Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR':
return False
return True
def execute(self, context):
clear_colors(self, context)
return {'FINISHED'}
return True
def execute(self, context):
clear_colors(self, context)
return {'FINISHED'}
def clear_colors(self, context):
obj = bpy.context.active_object
# Store previous mode
previous_mode = bpy.context.active_object.mode
if bpy.context.active_object.mode != 'EDIT':
bpy.ops.object.mode_set(mode='EDIT')
bm = bmesh.from_edit_mesh(bpy.context.active_object.data);
# Set all faces
for face in bm.faces:
face.material_index = 0
# Clear material slots
bpy.ops.object.mode_set(mode='OBJECT')
count = len(obj.material_slots)
for i in range(count):
bpy.ops.object.material_slot_remove()
# Delete materials if not used
for material in bpy.data.materials:
if utilities_color.material_prefix in material.name:
if material.users == 0:
material.user_clear()
bpy.data.materials.remove(material)
# Restore previous mode
bpy.ops.object.mode_set(mode=previous_mode)
for area in bpy.context.screen.areas:
print("area: {}".format(area.type))
if area.type == 'PROPERTIES':
for space in area.spaces:
if space.type == 'PROPERTIES':
# space.shading.type = 'MATERIAL'
space.context = 'MATERIAL'
# Show Material Tab
for area in bpy.context.screen.areas:
if area.type == 'PROPERTIES':
for space in area.spaces:
if space.type == 'PROPERTIES':
space.context = 'MATERIAL'
# Switch Solid shading
for area in bpy.context.screen.areas:
if area.type == 'VIEW_3D':
for space in area.spaces:
if space.type == 'VIEW_3D':
space.shading.type = 'SOLID'
obj = bpy.context.active_object
# Store previous mode
previous_mode = bpy.context.active_object.mode
if bpy.context.active_object.mode != 'EDIT':
bpy.ops.object.mode_set(mode='EDIT')
bm = bmesh.from_edit_mesh(bpy.context.active_object.data);
# Set all faces
for face in bm.faces:
face.material_index = 0
# Clear material slots
bpy.ops.object.mode_set(mode='OBJECT')
count = len(obj.material_slots)
for i in range(count):
bpy.ops.object.material_slot_remove()
# Delete materials if not used
for material in bpy.data.materials:
if utilities_color.material_prefix in material.name:
if material.users == 0:
material.user_clear()
bpy.data.materials.remove(material)
# Restore previous mode
bpy.ops.object.mode_set(mode=previous_mode)
for area in bpy.context.screen.areas:
print("area: {}".format(area.type))
if area.type == 'PROPERTIES':
for space in area.spaces:
if space.type == 'PROPERTIES':
# space.shading.type = 'MATERIAL'
space.context = 'MATERIAL'
# Show Material Tab
for area in bpy.context.screen.areas:
if area.type == 'PROPERTIES':
for space in area.spaces:
if space.type == 'PROPERTIES':
space.context = 'MATERIAL'
# Switch Solid shading
for area in bpy.context.screen.areas:
if area.type == 'VIEW_3D':
for space in area.spaces:
if space.type == 'VIEW_3D':
space.shading.type = 'SOLID'
bpy.utils.register_class(op)

@ -13,141 +13,141 @@ material_prefix = "TT_atlas_"
gamma = 2.2
class op(bpy.types.Operator):
bl_idname = "uv.textools_color_convert_to_texture"
bl_label = "Pack Texture"
bl_description = "Pack ID Colors into single texture and UVs"
bl_options = {'REGISTER', 'UNDO'}
bl_idname = "uv.textools_color_convert_to_texture"
bl_label = "Pack Texture"
bl_description = "Pack ID Colors into single texture and UVs"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
if not bpy.context.active_object:
return False
@classmethod
def poll(cls, context):
if not bpy.context.active_object:
return False
if bpy.context.active_object not in bpy.context.selected_objects:
return False
if bpy.context.active_object not in bpy.context.selected_objects:
return False
if len(bpy.context.selected_objects) != 1:
return False
if len(bpy.context.selected_objects) != 1:
return False
if bpy.context.active_object.type != 'MESH':
return False
if bpy.context.active_object.type != 'MESH':
return False
#Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR':
return False
#Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR':
return False
return True
def execute(self, context):
pack_texture(self, context)
return {'FINISHED'}
return True
def execute(self, context):
pack_texture(self, context)
return {'FINISHED'}
def pack_texture(self, context):
obj = bpy.context.active_object
name = material_prefix+obj.name
if obj.mode != 'OBJECT':
bpy.ops.object.mode_set(mode='OBJECT')
# Determine size
size_pixel = 8
size_square = math.ceil(math.sqrt( context.scene.texToolsSettings.color_ID_count ))
size_image = size_square * size_pixel
size_image_pow = int(math.pow(2, math.ceil(math.log(size_image, 2))))
# Maximize pixel size
size_pixel = math.floor(size_image_pow/size_square)
print("{0} colors = {1} x {1} = ({2}pix) {3} x {3} | {4} x {4}".format(
context.scene.texToolsSettings.color_ID_count,
size_square,
size_pixel,
size_image,
size_image_pow
))
# Create image
image = bpy.data.images.new(name, width=size_image_pow, height=size_image_pow)
pixels = [None] * size_image_pow * size_image_pow
# Black pixels
for x in range(size_image_pow):
for y in range(size_image_pow):
pixels[(y * size_image_pow) + x] = [0, 0, 0, 1]
# Pixels
for c in range(context.scene.texToolsSettings.color_ID_count):
x = c % size_square
y = math.floor(c/size_square)
color = utilities_color.get_color(c).copy()
for i in range(3):
color[i] = pow(color[i] , 1.0/gamma)
for sx in range(size_pixel):
for sy in range(size_pixel):
_x = x*size_pixel + sx
_y = y*size_pixel + sy
pixels[(_y * size_image_pow) + _x] = [color[0], color[1], color[2], 1]
# flatten list & assign pixels
pixels = [chan for px in pixels for chan in px]
image.pixels = pixels
# Set background image
for area in bpy.context.screen.areas:
if area.type == 'IMAGE_EDITOR':
area.spaces[0].image = image
# Edit mesh
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE')
bpy.ops.mesh.select_all(action='SELECT')
# bpy.ops.uv.smart_project(angle_limit=1)
bpy.ops.uv.unwrap(method='ANGLE_BASED', margin=0.0078)
bm = bmesh.from_edit_mesh(bpy.context.active_object.data)
uv_layers = bm.loops.layers.uv.verify();
for face in bm.faces:
index = face.material_index
# Get UV coordinates for index
x = index%size_square
y = math.floor(index/size_square)
x*= (size_pixel / size_image_pow)
y*= (size_pixel / size_image_pow)
x+= size_pixel/size_image_pow/2
y+= size_pixel/size_image_pow/2
for loop in face.loops:
loop[uv_layers].uv = (x, y)
# Remove Slots & add one
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.uv.textools_color_clear()
bpy.ops.object.material_slot_add()
#Create material with image
obj.material_slots[0].material = utilities_bake.get_image_material(image)
#Display UVs
bpy.ops.object.mode_set(mode='EDIT')
# Switch textured shading
for area in bpy.context.screen.areas:
if area.type == 'VIEW_3D':
for space in area.spaces:
if space.type == 'VIEW_3D':
space.shading.type = 'MATERIAL'
bpy.ops.ui.textools_popup('INVOKE_DEFAULT', message="Packed texture with {} color IDs".format( context.scene.texToolsSettings.color_ID_count ))
obj = bpy.context.active_object
name = material_prefix+obj.name
if obj.mode != 'OBJECT':
bpy.ops.object.mode_set(mode='OBJECT')
# Determine size
size_pixel = 8
size_square = math.ceil(math.sqrt( context.scene.texToolsSettings.color_ID_count ))
size_image = size_square * size_pixel
size_image_pow = int(math.pow(2, math.ceil(math.log(size_image, 2))))
# Maximize pixel size
size_pixel = math.floor(size_image_pow/size_square)
print("{0} colors = {1} x {1} = ({2}pix) {3} x {3} | {4} x {4}".format(
context.scene.texToolsSettings.color_ID_count,
size_square,
size_pixel,
size_image,
size_image_pow
))
# Create image
image = bpy.data.images.new(name, width=size_image_pow, height=size_image_pow)
pixels = [None] * size_image_pow * size_image_pow
# Black pixels
for x in range(size_image_pow):
for y in range(size_image_pow):
pixels[(y * size_image_pow) + x] = [0, 0, 0, 1]
# Pixels
for c in range(context.scene.texToolsSettings.color_ID_count):
x = c % size_square
y = math.floor(c/size_square)
color = utilities_color.get_color(c).copy()
for i in range(3):
color[i] = pow(color[i] , 1.0/gamma)
for sx in range(size_pixel):
for sy in range(size_pixel):
_x = x*size_pixel + sx
_y = y*size_pixel + sy
pixels[(_y * size_image_pow) + _x] = [color[0], color[1], color[2], 1]
# flatten list & assign pixels
pixels = [chan for px in pixels for chan in px]
image.pixels = pixels
# Set background image
for area in bpy.context.screen.areas:
if area.type == 'IMAGE_EDITOR':
area.spaces[0].image = image
# Edit mesh
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE')
bpy.ops.mesh.select_all(action='SELECT')
# bpy.ops.uv.smart_project(angle_limit=1)
bpy.ops.uv.unwrap(method='ANGLE_BASED', margin=0.0078)
bm = bmesh.from_edit_mesh(bpy.context.active_object.data)
uv_layers = bm.loops.layers.uv.verify();
for face in bm.faces:
index = face.material_index
# Get UV coordinates for index
x = index%size_square
y = math.floor(index/size_square)
x*= (size_pixel / size_image_pow)
y*= (size_pixel / size_image_pow)
x+= size_pixel/size_image_pow/2
y+= size_pixel/size_image_pow/2
for loop in face.loops:
loop[uv_layers].uv = (x, y)
# Remove Slots & add one
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.uv.textools_color_clear()
bpy.ops.object.material_slot_add()
#Create material with image
obj.material_slots[0].material = utilities_bake.get_image_material(image)
#Display UVs
bpy.ops.object.mode_set(mode='EDIT')
# Switch textured shading
for area in bpy.context.screen.areas:
if area.type == 'VIEW_3D':
for space in area.spaces:
if space.type == 'VIEW_3D':
space.shading.type = 'MATERIAL'
bpy.ops.ui.textools_popup('INVOKE_DEFAULT', message="Packed texture with {} color IDs".format( context.scene.texToolsSettings.color_ID_count ))
bpy.utils.register_class(op)

@ -12,79 +12,79 @@ from . import utilities_ui
gamma = 2.2
class op(bpy.types.Operator):
bl_idname = "uv.textools_color_convert_to_vertex_colors"
bl_label = "Pack Texture"
bl_description = "Pack ID Colors into single texture and UVs"
bl_options = {'REGISTER', 'UNDO'}
bl_idname = "uv.textools_color_convert_to_vertex_colors"
bl_label = "Pack Texture"
bl_description = "Pack ID Colors into single texture and UVs"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
if not bpy.context.active_object:
return False
@classmethod
def poll(cls, context):
if not bpy.context.active_object:
return False
if bpy.context.active_object not in bpy.context.selected_objects:
return False
if bpy.context.active_object not in bpy.context.selected_objects:
return False
if len(bpy.context.selected_objects) != 1:
return False
if len(bpy.context.selected_objects) != 1:
return False
if bpy.context.active_object.type != 'MESH':
return False
if bpy.context.active_object.type != 'MESH':
return False
#Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR':
return False
#Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR':
return False
return True
def execute(self, context):
convert_vertex_colors(self, context)
return {'FINISHED'}
return True
def execute(self, context):
convert_vertex_colors(self, context)
return {'FINISHED'}
def convert_vertex_colors(self, context):
obj = bpy.context.active_object
for i in range(len(obj.material_slots)):
slot = obj.material_slots[i]
if slot.material:
# Select related faces
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='DESELECT')
bm = bmesh.from_edit_mesh(bpy.context.active_object.data)
for face in bm.faces:
if face.material_index == i:
face.select = True
color = utilities_color.get_color(i).copy()
# Fix Gamma
color[0] = pow(color[0],1/gamma)
color[1] = pow(color[1],1/gamma)
color[2] = pow(color[2],1/gamma)
bpy.ops.object.mode_set(mode='VERTEX_PAINT')
bpy.context.tool_settings.vertex_paint.brush.color = color
bpy.context.object.data.use_paint_mask = True
bpy.ops.paint.vertex_color_set()
# Back to object mode
bpy.ops.object.mode_set(mode='VERTEX_PAINT')
bpy.context.object.data.use_paint_mask = False
# Switch textured shading
for area in bpy.context.screen.areas:
if area.type == 'VIEW_3D':
for space in area.spaces:
if space.type == 'VIEW_3D':
space.shading.type = 'MATERIAL'
# Clear any materials
bpy.ops.uv.textools_color_clear()
bpy.ops.ui.textools_popup('INVOKE_DEFAULT', message="Vertex colors assigned")
obj = bpy.context.active_object
for i in range(len(obj.material_slots)):
slot = obj.material_slots[i]
if slot.material:
# Select related faces
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='DESELECT')
bm = bmesh.from_edit_mesh(bpy.context.active_object.data)
for face in bm.faces:
if face.material_index == i:
face.select = True
color = utilities_color.get_color(i).copy()
# Fix Gamma
color[0] = pow(color[0],1/gamma)
color[1] = pow(color[1],1/gamma)
color[2] = pow(color[2],1/gamma)
bpy.ops.object.mode_set(mode='VERTEX_PAINT')
bpy.context.tool_settings.vertex_paint.brush.color = color
bpy.context.object.data.use_paint_mask = True
bpy.ops.paint.vertex_color_set()
# Back to object mode
bpy.ops.object.mode_set(mode='VERTEX_PAINT')
bpy.context.object.data.use_paint_mask = False
# Switch textured shading
for area in bpy.context.screen.areas:
if area.type == 'VIEW_3D':
for space in area.spaces:
if space.type == 'VIEW_3D':
space.shading.type = 'MATERIAL'
# Clear any materials
bpy.ops.uv.textools_color_clear()
bpy.ops.ui.textools_popup('INVOKE_DEFAULT', message="Vertex colors assigned")
bpy.utils.register_class(op)

@ -8,199 +8,199 @@ from math import pi
from . import utilities_color
class op(bpy.types.Operator):
bl_idname = "uv.textools_color_from_directions"
bl_label = "Color Directions"
bl_description = "Assign a color ID to different face directions"
bl_options = {'REGISTER', 'UNDO'}
directions : bpy.props.EnumProperty(items=
[('2', '2', 'Top & Bottom, Sides'),
('3', '3', 'Top & Bottom, Left & Right, Front & Back'),
('4', '4', 'Top, Left & Right, Front & Back, Bottom'),
('6', '6', 'All sides')],
name = "Directions",
default = '3'
)
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_dialog(self)
# def draw(self, context):
# layout = self.layout
# layout.prop(self, "directions")
@classmethod
def poll(cls, context):
if not bpy.context.active_object:
return False
if bpy.context.active_object not in bpy.context.selected_objects:
return False
if len(bpy.context.selected_objects) != 1:
return False
if bpy.context.active_object.type != 'MESH':
return False
#Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR':
return False
return True
def execute(self, context):
color_elements(self, context)
return {'FINISHED'}
bl_idname = "uv.textools_color_from_directions"
bl_label = "Color Directions"
bl_description = "Assign a color ID to different face directions"
bl_options = {'REGISTER', 'UNDO'}
directions : bpy.props.EnumProperty(items=
[('2', '2', 'Top & Bottom, Sides'),
('3', '3', 'Top & Bottom, Left & Right, Front & Back'),
('4', '4', 'Top, Left & Right, Front & Back, Bottom'),
('6', '6', 'All sides')],
name = "Directions",
default = '3'
)
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_dialog(self)
# def draw(self, context):
# layout = self.layout
# layout.prop(self, "directions")
@classmethod
def poll(cls, context):
if not bpy.context.active_object:
return False
if bpy.context.active_object not in bpy.context.selected_objects:
return False
if len(bpy.context.selected_objects) != 1:
return False
if bpy.context.active_object.type != 'MESH':
return False
#Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR':
return False
return True
def execute(self, context):
color_elements(self, context)
return {'FINISHED'}
def color_elements(self, context):
obj = bpy.context.active_object
# Setup Edit & Face mode
if obj.mode != 'EDIT':
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE')
# Collect groups
bm = bmesh.from_edit_mesh(bpy.context.active_object.data);
face_directions = {
'top':[],
'bottom':[],
'left':[],
'right':[],
'front':[],
'back':[]
}
print("Directions {}".format(self.directions))
for face in bm.faces:
print("face {} n: {}".format(face.index, face.normal))
# Find dominant direction
abs_x = abs(face.normal.x)
abs_y = abs(face.normal.y)
abs_z = abs(face.normal.z)
max_xyz = max(abs_x, abs_y, abs_z)
if max_xyz == abs_x:
if face.normal.x > 0:
face_directions['right'].append(face.index)
else:
face_directions['left'].append(face.index)
elif max_xyz == abs_y:
if face.normal.y > 0:
face_directions['front'].append(face.index)
else:
face_directions['back'].append(face.index)
elif max_xyz == abs_z:
if face.normal.z > 0:
face_directions['top'].append(face.index)
else:
face_directions['bottom'].append(face.index)
count = int(self.directions)
bpy.context.scene.texToolsSettings.color_ID_count = count
groups = []
# for i in range(count):
# groups.append([])
if self.directions == '2':
groups.append(face_directions['top']+face_directions['bottom'])
groups.append(face_directions['left']+face_directions['right']+face_directions['front']+face_directions['back'])
if self.directions == '3':
groups.append(face_directions['top']+face_directions['bottom'])
groups.append(face_directions['left']+face_directions['right'])
groups.append(face_directions['front']+face_directions['back'])
elif self.directions == '4':
groups.append(face_directions['top'])
groups.append(face_directions['left']+face_directions['right'])
groups.append(face_directions['front']+face_directions['back'])
groups.append(face_directions['bottom'])
elif self.directions == '6':
groups.append(face_directions['top'])
groups.append(face_directions['right'])
groups.append(face_directions['left'])
groups.append(face_directions['front'])
groups.append(face_directions['back'])
groups.append(face_directions['bottom'])
# Assign Groups to colors
index_color = 0
for group in groups:
# # rebuild bmesh data (e.g. left edit mode previous loop)
bm = bmesh.from_edit_mesh(bpy.context.active_object.data);
if hasattr(bm.faces, "ensure_lookup_table"):
bm.faces.ensure_lookup_table()
# Select group
bpy.ops.mesh.select_all(action='DESELECT')
for index_face in group:
bm.faces[index_face].select = True
# Assign to selection
bpy.ops.uv.textools_color_assign(index=index_color)
index_color = (index_color+1) % bpy.context.scene.texToolsSettings.color_ID_count
bpy.ops.object.mode_set(mode='OBJECT')
utilities_color.validate_face_colors(obj)
'''
faces_indices_processed = []
for face in bm.faces:
if face.index not in faces_indices_processed:
# Select face & extend
bpy.ops.mesh.select_all(action='DESELECT')
face.select = True
bpy.ops.mesh.select_linked(delimit={'NORMAL'})
faces = [f.index for f in bm.faces if (f.select and f.index not in faces_indices_processed)]
for f in faces:
faces_indices_processed.append(f)
groups.append(faces)
# Assign color count (caps automatically e.g. max 20)
bpy.context.scene.texToolsSettings.color_ID_count = len(groups)
gamma = 2.2
for i in range(bpy.context.scene.texToolsSettings.color_ID_count):
color = utilities_color.get_color_id(i, bpy.context.scene.texToolsSettings.color_ID_count)
# Fix Gamma
color[0] = pow(color[0] , gamma)
color[1] = pow(color[1] , gamma)
color[2] = pow(color[2], gamma)
utilities_color.set_color(i, color)
# Assign Groups to colors
index_color = 0
for group in groups:
# rebuild bmesh data (e.g. left edit mode previous loop)
bm = bmesh.from_edit_mesh(bpy.context.active_object.data);
if hasattr(bm.faces, "ensure_lookup_table"):
bm.faces.ensure_lookup_table()
# Select group
bpy.ops.mesh.select_all(action='DESELECT')
for index_face in group:
bm.faces[index_face].select = True
# Assign to selection
bpy.ops.uv.textools_color_assign(index=index_color)
index_color = (index_color+1) % bpy.context.scene.texToolsSettings.color_ID_count
bpy.ops.object.mode_set(mode='OBJECT')
utilities_color.validate_face_colors(obj)
'''
obj = bpy.context.active_object
# Setup Edit & Face mode
if obj.mode != 'EDIT':
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE')
# Collect groups
bm = bmesh.from_edit_mesh(bpy.context.active_object.data);
face_directions = {
'top':[],
'bottom':[],
'left':[],
'right':[],
'front':[],
'back':[]
}
print("Directions {}".format(self.directions))
for face in bm.faces:
print("face {} n: {}".format(face.index, face.normal))
# Find dominant direction
abs_x = abs(face.normal.x)
abs_y = abs(face.normal.y)
abs_z = abs(face.normal.z)
max_xyz = max(abs_x, abs_y, abs_z)
if max_xyz == abs_x:
if face.normal.x > 0:
face_directions['right'].append(face.index)
else:
face_directions['left'].append(face.index)
elif max_xyz == abs_y:
if face.normal.y > 0:
face_directions['front'].append(face.index)
else:
face_directions['back'].append(face.index)
elif max_xyz == abs_z:
if face.normal.z > 0:
face_directions['top'].append(face.index)
else:
face_directions['bottom'].append(face.index)
count = int(self.directions)
bpy.context.scene.texToolsSettings.color_ID_count = count
groups = []
# for i in range(count):
# groups.append([])
if self.directions == '2':
groups.append(face_directions['top']+face_directions['bottom'])
groups.append(face_directions['left']+face_directions['right']+face_directions['front']+face_directions['back'])
if self.directions == '3':
groups.append(face_directions['top']+face_directions['bottom'])
groups.append(face_directions['left']+face_directions['right'])
groups.append(face_directions['front']+face_directions['back'])
elif self.directions == '4':
groups.append(face_directions['top'])
groups.append(face_directions['left']+face_directions['right'])
groups.append(face_directions['front']+face_directions['back'])
groups.append(face_directions['bottom'])
elif self.directions == '6':
groups.append(face_directions['top'])
groups.append(face_directions['right'])
groups.append(face_directions['left'])
groups.append(face_directions['front'])
groups.append(face_directions['back'])
groups.append(face_directions['bottom'])
# Assign Groups to colors
index_color = 0
for group in groups:
# # rebuild bmesh data (e.g. left edit mode previous loop)
bm = bmesh.from_edit_mesh(bpy.context.active_object.data);
if hasattr(bm.faces, "ensure_lookup_table"):
bm.faces.ensure_lookup_table()
# Select group
bpy.ops.mesh.select_all(action='DESELECT')
for index_face in group:
bm.faces[index_face].select = True
# Assign to selection
bpy.ops.uv.textools_color_assign(index=index_color)
index_color = (index_color+1) % bpy.context.scene.texToolsSettings.color_ID_count
bpy.ops.object.mode_set(mode='OBJECT')
utilities_color.validate_face_colors(obj)
'''
faces_indices_processed = []
for face in bm.faces:
if face.index not in faces_indices_processed:
# Select face & extend
bpy.ops.mesh.select_all(action='DESELECT')
face.select = True
bpy.ops.mesh.select_linked(delimit={'NORMAL'})
faces = [f.index for f in bm.faces if (f.select and f.index not in faces_indices_processed)]
for f in faces:
faces_indices_processed.append(f)
groups.append(faces)
# Assign color count (caps automatically e.g. max 20)
bpy.context.scene.texToolsSettings.color_ID_count = len(groups)
gamma = 2.2
for i in range(bpy.context.scene.texToolsSettings.color_ID_count):
color = utilities_color.get_color_id(i, bpy.context.scene.texToolsSettings.color_ID_count)
# Fix Gamma
color[0] = pow(color[0] , gamma)
color[1] = pow(color[1] , gamma)
color[2] = pow(color[2], gamma)
utilities_color.set_color(i, color)
# Assign Groups to colors
index_color = 0
for group in groups:
# rebuild bmesh data (e.g. left edit mode previous loop)
bm = bmesh.from_edit_mesh(bpy.context.active_object.data);
if hasattr(bm.faces, "ensure_lookup_table"):
bm.faces.ensure_lookup_table()
# Select group
bpy.ops.mesh.select_all(action='DESELECT')
for index_face in group:
bm.faces[index_face].select = True
# Assign to selection
bpy.ops.uv.textools_color_assign(index=index_color)
index_color = (index_color+1) % bpy.context.scene.texToolsSettings.color_ID_count
bpy.ops.object.mode_set(mode='OBJECT')
utilities_color.validate_face_colors(obj)
'''
bpy.utils.register_class(op)

@ -8,95 +8,95 @@ from math import pi
from . import utilities_color
class op(bpy.types.Operator):
bl_idname = "uv.textools_color_from_elements"
bl_label = "Color Elements"
bl_description = "Assign a color ID to each mesh element"
bl_options = {'REGISTER', 'UNDO'}
bl_idname = "uv.textools_color_from_elements"
bl_label = "Color Elements"
bl_description = "Assign a color ID to each mesh element"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
if not bpy.context.active_object:
return False
@classmethod
def poll(cls, context):
if not bpy.context.active_object:
return False
if bpy.context.active_object not in bpy.context.selected_objects:
return False
if bpy.context.active_object not in bpy.context.selected_objects:
return False
if len(bpy.context.selected_objects) != 1:
return False
if len(bpy.context.selected_objects) != 1:
return False
if bpy.context.active_object.type != 'MESH':
return False
if bpy.context.active_object.type != 'MESH':
return False
#Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR':
return False
#Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR':
return False
return True
def execute(self, context):
color_elements(self, context)
return {'FINISHED'}
return True
def execute(self, context):
color_elements(self, context)
return {'FINISHED'}
def color_elements(self, context):
obj = bpy.context.active_object
# Setup Edit & Face mode
if obj.mode != 'EDIT':
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE')
# Collect groups
bm = bmesh.from_edit_mesh(bpy.context.active_object.data);
faces_indices_processed = []
groups = []
for face in bm.faces:
if face.index not in faces_indices_processed:
# Select face & extend
bpy.ops.mesh.select_all(action='DESELECT')
face.select = True
bpy.ops.mesh.select_linked(delimit={'NORMAL'})
faces = [f.index for f in bm.faces if (f.select and f.index not in faces_indices_processed)]
for f in faces:
faces_indices_processed.append(f)
groups.append(faces)
# Assign color count (caps automatically e.g. max 20)
bpy.context.scene.texToolsSettings.color_ID_count = len(groups)
gamma = 2.2
for i in range(bpy.context.scene.texToolsSettings.color_ID_count):
color = utilities_color.get_color_id(i, bpy.context.scene.texToolsSettings.color_ID_count)
# Fix Gamma
color[0] = pow(color[0] , gamma)
color[1] = pow(color[1] , gamma)
color[2] = pow(color[2], gamma)
utilities_color.set_color(i, color)
# Assign Groups to colors
index_color = 0
for group in groups:
# rebuild bmesh data (e.g. left edit mode previous loop)
bm = bmesh.from_edit_mesh(bpy.context.active_object.data);
if hasattr(bm.faces, "ensure_lookup_table"):
bm.faces.ensure_lookup_table()
# Select group
bpy.ops.mesh.select_all(action='DESELECT')
for index_face in group:
bm.faces[index_face].select = True
# Assign to selection
bpy.ops.uv.textools_color_assign(index=index_color)
index_color = (index_color+1) % bpy.context.scene.texToolsSettings.color_ID_count
bpy.ops.object.mode_set(mode='OBJECT')
utilities_color.validate_face_colors(obj)
obj = bpy.context.active_object
# Setup Edit & Face mode
if obj.mode != 'EDIT':
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE')
# Collect groups
bm = bmesh.from_edit_mesh(bpy.context.active_object.data);
faces_indices_processed = []
groups = []
for face in bm.faces:
if face.index not in faces_indices_processed:
# Select face & extend
bpy.ops.mesh.select_all(action='DESELECT')
face.select = True
bpy.ops.mesh.select_linked(delimit={'NORMAL'})
faces = [f.index for f in bm.faces if (f.select and f.index not in faces_indices_processed)]
for f in faces:
faces_indices_processed.append(f)
groups.append(faces)
# Assign color count (caps automatically e.g. max 20)
bpy.context.scene.texToolsSettings.color_ID_count = len(groups)
gamma = 2.2
for i in range(bpy.context.scene.texToolsSettings.color_ID_count):
color = utilities_color.get_color_id(i, bpy.context.scene.texToolsSettings.color_ID_count)
# Fix Gamma
color[0] = pow(color[0] , gamma)
color[1] = pow(color[1] , gamma)
color[2] = pow(color[2], gamma)
utilities_color.set_color(i, color)
# Assign Groups to colors
index_color = 0
for group in groups:
# rebuild bmesh data (e.g. left edit mode previous loop)
bm = bmesh.from_edit_mesh(bpy.context.active_object.data);
if hasattr(bm.faces, "ensure_lookup_table"):
bm.faces.ensure_lookup_table()
# Select group
bpy.ops.mesh.select_all(action='DESELECT')
for index_face in group:
bm.faces[index_face].select = True
# Assign to selection
bpy.ops.uv.textools_color_assign(index=index_color)
index_color = (index_color+1) % bpy.context.scene.texToolsSettings.color_ID_count
bpy.ops.object.mode_set(mode='OBJECT')
utilities_color.validate_face_colors(obj)
bpy.utils.register_class(op)

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

@ -9,34 +9,34 @@ from . import utilities_color
class op(bpy.types.Operator):
bl_idname = "uv.textools_color_io_export"
bl_label = "Export"
bl_description = "Export current color palette to clipboard"
bl_idname = "uv.textools_color_io_export"
bl_label = "Export"
bl_description = "Export current color palette to clipboard"
@classmethod
def poll(cls, context):
#Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR':
return False
@classmethod
def poll(cls, context):
#Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR':
return False
return True
def execute(self, context):
export_colors(self, context)
return {'FINISHED'}
return True
def execute(self, context):
export_colors(self, context)
return {'FINISHED'}
def export_colors(self, context):
hex_colors = []
for i in range(bpy.context.scene.texToolsSettings.color_ID_count):
color = getattr(bpy.context.scene.texToolsSettings, "color_ID_color_{}".format(i))
hex_colors.append( utilities_color.color_to_hex( color) )
bpy.context.window_manager.clipboard = ", ".join(hex_colors)
bpy.ops.ui.textools_popup('INVOKE_DEFAULT', message="{}x colors copied to clipboard".format(len(hex_colors)))
hex_colors = []
for i in range(bpy.context.scene.texToolsSettings.color_ID_count):
color = getattr(bpy.context.scene.texToolsSettings, "color_ID_color_{}".format(i))
hex_colors.append( utilities_color.color_to_hex( color) )
bpy.context.window_manager.clipboard = ", ".join(hex_colors)
bpy.ops.ui.textools_popup('INVOKE_DEFAULT', message="{}x colors copied to clipboard".format(len(hex_colors)))
bpy.utils.register_class(op)

@ -9,53 +9,53 @@ from math import pi
from . import utilities_color
class op(bpy.types.Operator):
bl_idname = "uv.textools_color_io_import"
bl_label = "Import"
bl_description = "Import hex colors from the clipboard as current color palette"
bl_idname = "uv.textools_color_io_import"
bl_label = "Import"
bl_description = "Import hex colors from the clipboard as current color palette"
@classmethod
def poll(cls, context):
@classmethod
def poll(cls, context):
#Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR':
return False
#Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR':
return False
return True
def execute(self, context):
import_colors(self, context)
return {'FINISHED'}
return True
def execute(self, context):
import_colors(self, context)
return {'FINISHED'}
def import_colors(self, context):
# Clipboard hex strings
hex_strings = bpy.context.window_manager.clipboard.split(',')
for i in range(len(hex_strings)):
hex_strings[i] = hex_strings[i].strip().strip('#')
if len(hex_strings[i]) != 6 or not all(c in string.hexdigits for c in hex_strings[i]):
# Incorrect format
self.report({'ERROR_INVALID_INPUT'}, "Incorrect hex format '{}' use a #RRGGBB pattern".format(hex_strings[i]))
return
else:
name = "color_ID_color_{}".format(i)
if hasattr(bpy.context.scene.texToolsSettings, name):
# Color Index exists
color = utilities_color.hex_to_color( hex_strings[i] )
setattr(bpy.context.scene.texToolsSettings, name, color)
else:
# More colors imported than supported
self.report({'ERROR_INVALID_INPUT'}, "Only {}x colors have been imported instead of {}x".format(
i,len(hex_strings)
))
return
# Set number of colors
bpy.context.scene.texToolsSettings.color_ID_count = len(hex_strings)
bpy.ops.ui.textools_popup('INVOKE_DEFAULT', message="{}x colors imported from clipboard".format( len(hex_strings) ))
# Clipboard hex strings
hex_strings = bpy.context.window_manager.clipboard.split(',')
for i in range(len(hex_strings)):
hex_strings[i] = hex_strings[i].strip().strip('#')
if len(hex_strings[i]) != 6 or not all(c in string.hexdigits for c in hex_strings[i]):
# Incorrect format
self.report({'ERROR_INVALID_INPUT'}, "Incorrect hex format '{}' use a #RRGGBB pattern".format(hex_strings[i]))
return
else:
name = "color_ID_color_{}".format(i)
if hasattr(bpy.context.scene.texToolsSettings, name):
# Color Index exists
color = utilities_color.hex_to_color( hex_strings[i] )
setattr(bpy.context.scene.texToolsSettings, name, color)
else:
# More colors imported than supported
self.report({'ERROR_INVALID_INPUT'}, "Only {}x colors have been imported instead of {}x".format(
i,len(hex_strings)
))
return
# Set number of colors
bpy.context.scene.texToolsSettings.color_ID_count = len(hex_strings)
bpy.ops.ui.textools_popup('INVOKE_DEFAULT', message="{}x colors imported from clipboard".format( len(hex_strings) ))
bpy.utils.register_class(op)

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

@ -14,276 +14,276 @@ from . import utilities_uv
class op(bpy.types.Operator):
bl_idname = "uv.textools_edge_split_bevel"
bl_label = "Split Bevel"
bl_description = "..."
bl_options = {'REGISTER', 'UNDO'}
bl_idname = "uv.textools_edge_split_bevel"
bl_label = "Split Bevel"
bl_description = "..."
bl_options = {'REGISTER', 'UNDO'}
radius : bpy.props.FloatProperty(
name = "Space",
description = "Space for split bevel",
default = 0.015,
min = 0,
max = 0.35
)
radius : bpy.props.FloatProperty(
name = "Space",
description = "Space for split bevel",
default = 0.015,
min = 0,
max = 0.35
)
@classmethod
def poll(cls, context):
if not bpy.context.active_object:
return False
@classmethod
def poll(cls, context):
if not bpy.context.active_object:
return False
#Only in Edit mode
if bpy.context.active_object.mode != 'EDIT':
return False
#Only in Edit mode
if bpy.context.active_object.mode != 'EDIT':
return False
#Requires UV map
if not bpy.context.object.data.uv_layers:
return False
#Requires UV map
if not bpy.context.object.data.uv_layers:
return False
if bpy.context.scene.tool_settings.use_uv_select_sync:
return False
if bpy.context.scene.tool_settings.use_uv_select_sync:
return False
#Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR':
return False
#Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR':
return False
return True
return True
def execute(self, context):
main(self, self.radius)
return {'FINISHED'}
def execute(self, context):
main(self, self.radius)
return {'FINISHED'}
def main(self, radius):
#Store selection
utilities_uv.selection_store()
#Store selection
utilities_uv.selection_store()
print("____________\nedge split UV sharp edges {}".format(radius))
print("____________\nedge split UV sharp edges {}".format(radius))
obj = bpy.context.object
bm = bmesh.from_edit_mesh(bpy.context.active_object.data);
uv_layers = bm.loops.layers.uv.verify();
islands = utilities_uv.getSelectionIslands()
obj = bpy.context.object
bm = bmesh.from_edit_mesh(bpy.context.active_object.data);
uv_layers = bm.loops.layers.uv.verify();
islands = utilities_uv.getSelectionIslands()
# Collect UV to Vert
vert_to_uv = utilities_uv.get_vert_to_uv(bm, uv_layers)
uv_to_vert = utilities_uv.get_uv_to_vert(bm, uv_layers)
# Collect UV to Vert
vert_to_uv = utilities_uv.get_vert_to_uv(bm, uv_layers)
uv_to_vert = utilities_uv.get_uv_to_vert(bm, uv_layers)
# Collect hard edges
edges = []
for edge in bm.edges:
if edge.select and not edge.smooth:
# edge.link_faces
# print("Hard edge: {} - {}".format(edge.verts[0].index, edge.verts[1].index))
edges.append(edge)
# Collect hard edges
edges = []
for edge in bm.edges:
if edge.select and not edge.smooth:
# edge.link_faces
# print("Hard edge: {} - {}".format(edge.verts[0].index, edge.verts[1].index))
edges.append(edge)
# Get vert rails to slide
vert_rails = get_vert_edge_rails(edges)
# Get vert rails to slide
vert_rails = get_vert_edge_rails(edges)
# Get left and right faces
edge_face_pairs = get_edge_face_pairs(edges)
# Get left and right faces
edge_face_pairs = get_edge_face_pairs(edges)
print("Vert rails: {}x".format(len(vert_rails)))
# for vert in vert_rails:
# print(".. v.idx {} = {}x".format(vert.index, len(vert_rails[vert]) ))
print("Vert rails: {}x".format(len(vert_rails)))
# for vert in vert_rails:
# print(".. v.idx {} = {}x".format(vert.index, len(vert_rails[vert]) ))
vert_processed = []
vert_uv_pos = []
vert_processed = []
vert_uv_pos = []
for edge in edges:
if len(edge_face_pairs[edge]) == 2:
v0 = edge.verts[0]
v1 = edge.verts[1]
f0 = edge_face_pairs[edge][0]
f1 = edge_face_pairs[edge][1]
for edge in edges:
if len(edge_face_pairs[edge]) == 2:
v0 = edge.verts[0]
v1 = edge.verts[1]
f0 = edge_face_pairs[edge][0]
f1 = edge_face_pairs[edge][1]
# v0
if v0 not in vert_processed:
vert_processed.append(v0)
faces, origin, delta = slide_uvs(v0, edge, f0, edges, vert_rails, vert_to_uv)
vert_uv_pos.append( {"v":v0, "f":f0, "origin":origin, "delta":delta, "faces":faces} )
# v0
if v0 not in vert_processed:
vert_processed.append(v0)
faces, origin, delta = slide_uvs(v0, edge, f0, edges, vert_rails, vert_to_uv)
vert_uv_pos.append( {"v":v0, "f":f0, "origin":origin, "delta":delta, "faces":faces} )
faces, origin, delta = slide_uvs(v0, edge, f1, edges, vert_rails, vert_to_uv)
vert_uv_pos.append( {"v":v0, "f":f1, "origin":origin, "delta":delta, "faces":faces} )
faces, origin, delta = slide_uvs(v0, edge, f1, edges, vert_rails, vert_to_uv)
vert_uv_pos.append( {"v":v0, "f":f1, "origin":origin, "delta":delta, "faces":faces} )
# V1
if v1 not in vert_processed:
vert_processed.append(v1)
faces, origin, delta = slide_uvs(v1, edge, f0, edges, vert_rails, vert_to_uv)
vert_uv_pos.append( {"v":v1, "f":f0, "origin":origin, "delta":delta, "faces":faces} )
faces, origin, delta = slide_uvs(v1, edge, f1, edges, vert_rails, vert_to_uv)
vert_uv_pos.append( {"v":v1, "f":f1, "origin":origin, "delta":delta, "faces":faces} )
# ...
for item in vert_uv_pos:
v = item["v"]
# V1
if v1 not in vert_processed:
vert_processed.append(v1)
faces, origin, delta = slide_uvs(v1, edge, f0, edges, vert_rails, vert_to_uv)
vert_uv_pos.append( {"v":v1, "f":f0, "origin":origin, "delta":delta, "faces":faces} )
faces, origin, delta = slide_uvs(v1, edge, f1, edges, vert_rails, vert_to_uv)
vert_uv_pos.append( {"v":v1, "f":f1, "origin":origin, "delta":delta, "faces":faces} )
# ...
for item in vert_uv_pos:
v = item["v"]
for face in item["faces"]:
if v in face.verts:
for loop in face.loops:
if loop.vert == v:
loop[uv_layers].uv= item["origin"] + item["delta"] * (radius/2)
# for f in faces:
# for loop in f.loops:
# if loop.vert == vert:
# loop[uv_layers].uv= vert_to_uv[vert][0].uv + item["delta"] * radius/2
for face in item["faces"]:
if v in face.verts:
for loop in face.loops:
if loop.vert == v:
loop[uv_layers].uv= item["origin"] + item["delta"] * (radius/2)
# for f in faces:
# for loop in f.loops:
# if loop.vert == vert:
# loop[uv_layers].uv= vert_to_uv[vert][0].uv + item["delta"] * radius/2
# for loop in face.loops:
# if loop.vert == vert:
# loop[uv_layers].uv+= avg_uv_delta
# for loop in face.loops:
# if loop.vert == vert:
# loop[uv_layers].uv+= avg_uv_delta
#Restore selection
utilities_uv.selection_restore()
#Restore selection
utilities_uv.selection_restore()
def slide_uvs(vert, edge, face, edges, vert_rails, vert_to_uv):
def IS_DEBUG():
return vert.index == 64 and edge.verts[0].index == 64 and edge.verts[1].index == 63
A = edge.verts[0]
B = edge.verts[1]
A_links, B_links = get_edge_prev_next(edge, edges)
verts_edges = {edge.verts[0], edge.verts[1]}
for v in A_links:
verts_edges.add( v )
for v in B_links:
verts_edges.add( v )
def IS_DEBUG():
return vert.index == 64 and edge.verts[0].index == 64 and edge.verts[1].index == 63
A = edge.verts[0]
B = edge.verts[1]
A_links, B_links = get_edge_prev_next(edge, edges)
verts_edges = {edge.verts[0], edge.verts[1]}
for v in A_links:
verts_edges.add( v )
for v in B_links:
verts_edges.add( v )
if IS_DEBUG():
print("\r")
if IS_DEBUG():
print("\r")
print("Edge {} <--> {} ({})".format(edge.verts[0].index, edge.verts[1].index , vert.index))
print("Edge {} <--> {} ({})".format(edge.verts[0].index, edge.verts[1].index , vert.index))
# Collect faces of this side
# Collect faces of this side
'''
faces = [face]
face_edges_used = [e for e in face.edges if e in edges]
for e in face.edges:
if e not in face_edges_used:
for f in e.link_faces:
if f != face:
faces.append(f)
'''
'''
faces = [face]
face_edges_used = [e for e in face.edges if e in edges]
for e in face.edges:
if e not in face_edges_used:
for f in e.link_faces:
if f != face:
faces.append(f)
'''
faces = [face]
edges_main_used = [edge]
for i in range(2):
append = []
faces = [face]
edges_main_used = [edge]
for i in range(2):
append = []
for f in faces:
for e in f.edges:
if e not in edges_main_used:
if e in edges:
edges_main_used.append(e)
for f in faces:
for e in f.edges:
if e not in edges_main_used:
if e in edges:
edges_main_used.append(e)
for f_link in e.link_faces:
if f_link not in faces:
append.append(f_link)
faces.extend(append)
for f_link in e.link_faces:
if f_link not in faces:
append.append(f_link)
faces.extend(append)
if IS_DEBUG():
print(" Faces {}x = {}".format(len(faces), [f.index for f in faces]))
if IS_DEBUG():
print(" Faces {}x = {}".format(len(faces), [f.index for f in faces]))
# Get all face edges that could be valid rails
face_edges = list(set([e for f in faces for e in f.edges if e not in edges]))
# Get all face edges that could be valid rails
face_edges = list(set([e for f in faces for e in f.edges if e not in edges]))
# The verts influencing the offset
verts = [A,B]
if vert == A:
verts.extend(B_links)
elif vert == B:
verts.extend(A_links)
# verts = [vert]
# The verts influencing the offset
verts = [A,B]
if vert == A:
verts.extend(B_links)
elif vert == B:
verts.extend(A_links)
# verts = [vert]
if IS_DEBUG():
print(" Verts: {}x = {}".format(len(verts), [v.index for v in verts]))
print(" Rails:")
if IS_DEBUG():
print(" Verts: {}x = {}".format(len(verts), [v.index for v in verts]))
print(" Rails:")
delta = Vector((0,0))
count = 0.0
for v in verts:
rails = [e for e in vert_rails[v] if e in face_edges]
delta = Vector((0,0))
count = 0.0
for v in verts:
rails = [e for e in vert_rails[v] if e in face_edges]
if IS_DEBUG():
print(" #{} rails = {}".format(v.index, [("{} - {}".format(e.verts[0].index, e.verts[1].index)) for e in rails]))
if IS_DEBUG():
print(" #{} rails = {}".format(v.index, [("{} - {}".format(e.verts[0].index, e.verts[1].index)) for e in rails]))
for e in rails:
# determine order
v0 = None
v1 = None
if e.verts[0] in verts_edges:
v0 = e.verts[0]
v1 = e.verts[1]
elif e.verts[1] in verts_edges:
v0 = e.verts[1]
v1 = e.verts[0]
uv0 = vert_to_uv[v0][0].uv
uv1 = vert_to_uv[v1][0].uv
delta += (uv1-uv0).normalized()
count += 1.0
for e in rails:
# determine order
v0 = None
v1 = None
if e.verts[0] in verts_edges:
v0 = e.verts[0]
v1 = e.verts[1]
elif e.verts[1] in verts_edges:
v0 = e.verts[1]
v1 = e.verts[0]
uv0 = vert_to_uv[v0][0].uv
uv1 = vert_to_uv[v1][0].uv
delta += (uv1-uv0).normalized()
count += 1.0
delta/=count
delta/=count
if IS_DEBUG():
print("\r")
if IS_DEBUG():
print("\r")
return faces, vert_to_uv[vert][0].uv.copy(), delta.normalized()
# print(" V{} = {}".format(v.index, avg_uv_delta))
return faces, vert_to_uv[vert][0].uv.copy(), delta.normalized()
# print(" V{} = {}".format(v.index, avg_uv_delta))
# for loop in face.loops:
# if loop.vert == vert:
# loop[uv_layers].uv+= avg_uv_delta
# for loop in face.loops:
# if loop.vert == vert:
# loop[uv_layers].uv+= avg_uv_delta
'''
def slide_face_uvs(uv_layers, edge, vert, face, radius, vert_to_uv):
avg_target = Vector((0,0))
avg_count = 0
for e in face.edges:
if e != edge and vert in e.verts:
vert_B = e.verts[0]
if vert == e.verts[0]:
vert_B = e.verts[1]
A = vert_to_uv[vert][0].uv
B = vert_to_uv[vert_B][0].uv
avg_target+= A +(B - A).normalized() * radius
avg_count+=1
avg_target/=avg_count
avg_target = vert_to_uv[vert][0].uv +(avg_target - vert_to_uv[vert][0].uv).normalized() * radius
for loop in face.loops:
if loop.vert == vert:
loop[uv_layers].uv = avg_target
avg_target = Vector((0,0))
avg_count = 0
for e in face.edges:
if e != edge and vert in e.verts:
vert_B = e.verts[0]
if vert == e.verts[0]:
vert_B = e.verts[1]
A = vert_to_uv[vert][0].uv
B = vert_to_uv[vert_B][0].uv
avg_target+= A +(B - A).normalized() * radius
avg_count+=1
avg_target/=avg_count
avg_target = vert_to_uv[vert][0].uv +(avg_target - vert_to_uv[vert][0].uv).normalized() * radius
for loop in face.loops:
if loop.vert == vert:
loop[uv_layers].uv = avg_target
'''
@ -294,82 +294,82 @@ def slide_face_uvs(uv_layers, edge, vert, face, radius, vert_to_uv):
'''
# Get all rails (max 3x: current, before and after)
rails = [e for v in verts_edges for e in vert_rails[v]]
# Get all rails (max 3x: current, before and after)
rails = [e for v in verts_edges for e in vert_rails[v]]
for x in rails:
print(" raail: {} x {}".format(x.verts[0].index, x.verts[1].index))
for x in rails:
print(" raail: {} x {}".format(x.verts[0].index, x.verts[1].index))
# Keep only rails shared with faces
rails = [e for e in rails if e in face_edges]
# Keep only rails shared with faces
rails = [e for e in rails if e in face_edges]
# print("...... v{} with {}x rails ".format(vert.index, len(rails)))
# print("...... v{} with {}x rails ".format(vert.index, len(rails)))
# Filter rails on same side
# Filter rails on same side
'''
def get_edge_prev_next(edge, edges):
A = edge.verts[0]
B = edge.verts[1]
A = edge.verts[0]
B = edge.verts[1]
# print(" get_edge_prev_next {}x edges".format(len(edges)))
# v0_extends = []
# v0_extends = [v for e in edges for v in e.verts if v in edge.verts and e != edge and v != v0]
# v1_extends = [v for e in edges for v in e.verts if v in edge.verts and e != edge and v != v1]
# v0_extends = [v_nest for v in edge.verts for e in v.link_edges for v_nest in e.verts if e != edge and if e in edges]
A_extends = [v2 for v1 in edge.verts for e in v1.link_edges for v2 in e.verts if e != edge and e in edges and v2 not in edge.verts and v1 != A]
B_extends = [v2 for v1 in edge.verts for e in v1.link_edges for v2 in e.verts if e != edge and e in edges and v2 not in edge.verts and v1 != B]
# print(" get_edge_prev_next {}x edges".format(len(edges)))
# v0_extends = []
# v0_extends = [v for e in edges for v in e.verts if v in edge.verts and e != edge and v != v0]
# v1_extends = [v for e in edges for v in e.verts if v in edge.verts and e != edge and v != v1]
# v0_extends = [v_nest for v in edge.verts for e in v.link_edges for v_nest in e.verts if e != edge and if e in edges]
A_extends = [v2 for v1 in edge.verts for e in v1.link_edges for v2 in e.verts if e != edge and e in edges and v2 not in edge.verts and v1 != A]
B_extends = [v2 for v1 in edge.verts for e in v1.link_edges for v2 in e.verts if e != edge and e in edges and v2 not in edge.verts and v1 != B]
return A_extends, B_extends
return A_extends, B_extends
def get_edge_face_pairs(edges):
edge_faces = {}
for edge in edges:
v0 = edge.verts[0]
v1 = edge.verts[1]
faces = []
for face in edge.link_faces:
if v0 in face.verts and v1 in face.verts:
faces.append(face)
edge_faces[edge] = faces
edge_faces = {}
for edge in edges:
v0 = edge.verts[0]
v1 = edge.verts[1]
faces = []
for face in edge.link_faces:
if v0 in face.verts and v1 in face.verts:
faces.append(face)
edge_faces[edge] = faces
return edge_faces
return edge_faces
def get_vert_edge_rails(edges):
vert_rails = {}
for edge in edges:
v0 = edge.verts[0]
v1 = edge.verts[1]
vert_rails = {}
for edge in edges:
v0 = edge.verts[0]
v1 = edge.verts[1]
faces = []
for face in edge.link_faces:
if v0 in face.verts and v1 in face.verts:
faces.append(face)
faces = []
for face in edge.link_faces:
if v0 in face.verts and v1 in face.verts:
faces.append(face)
for face in faces:
for e in face.edges:
if e not in edges and len(e.link_faces) > 0:
if v0 not in vert_rails:
vert_rails[ v0 ] = []
if v1 not in vert_rails:
vert_rails[ v1 ] = []
for face in faces:
for e in face.edges:
if e not in edges and len(e.link_faces) > 0:
if v0 not in vert_rails:
vert_rails[ v0 ] = []
if v1 not in vert_rails:
vert_rails[ v1 ] = []
if v0 in e.verts and e not in vert_rails[v0]:
vert_rails[v0].append(e)
if v0 in e.verts and e not in vert_rails[v0]:
vert_rails[v0].append(e)
if v1 in e.verts and e not in vert_rails[v1]:
vert_rails[v1].append(e)
if v1 in e.verts and e not in vert_rails[v1]:
vert_rails[v1].append(e)
return vert_rails
return vert_rails
bpy.utils.register_class(op)

@ -9,134 +9,134 @@ from math import pi
from . import utilities_uv
class op(bpy.types.Operator):
bl_idname = "uv.textools_island_align_edge"
bl_label = "Align Island by Edge"
bl_description = "Align the island by selected edge"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
#Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR':
return False
bl_idname = "uv.textools_island_align_edge"
bl_label = "Align Island by Edge"
bl_description = "Align the island by selected edge"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
#Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR':
return False
if not bpy.context.active_object:
return False
if not bpy.context.active_object:
return False
if bpy.context.active_object.type != 'MESH':
return False
if bpy.context.active_object.type != 'MESH':
return False
#Only in Edit mode
if bpy.context.active_object.mode != 'EDIT':
return False
#Only in Edit mode
if bpy.context.active_object.mode != 'EDIT':
return False
if bpy.context.scene.tool_settings.use_uv_select_sync:
return False
if bpy.context.scene.tool_settings.use_uv_select_sync:
return False
#Requires UV map
if not bpy.context.object.data.uv_layers:
return False
#Requires UV map
if not bpy.context.object.data.uv_layers:
return False
# Requires UV Edge select mode
if bpy.context.scene.tool_settings.uv_select_mode != 'EDGE':
return False
# Requires UV Edge select mode
if bpy.context.scene.tool_settings.uv_select_mode != 'EDGE':
return False
return True
return True
def execute(self, context):
#Store selection
utilities_uv.selection_store()
def execute(self, context):
#Store selection
utilities_uv.selection_store()
main(context)
main(context)
#Restore selection
utilities_uv.selection_restore()
#Restore selection
utilities_uv.selection_restore()
return {'FINISHED'}
return {'FINISHED'}
def main(context):
print("Executing operator_island_align_edge")
bm = bmesh.from_edit_mesh(bpy.context.active_object.data)
uv_layers = bm.loops.layers.uv.verify()
faces_selected = [];
for face in bm.faces:
if face.select:
for loop in face.loops:
if loop[uv_layers].select:
faces_selected.append(face)
break
print("faces_selected: "+str(len(faces_selected)))
# Collect 2 uv verts for each island
face_uvs = {}
for face in faces_selected:
uvs = []
for loop in face.loops:
if loop[uv_layers].select:
uvs.append(loop[uv_layers])
if len(uvs) >= 2:
break
if len(uvs) >= 2:
face_uvs[face] = uvs
faces_islands = {}
faces_unparsed = faces_selected.copy()
for face in face_uvs:
if face in faces_unparsed:
bpy.ops.uv.select_all(action='DESELECT')
face_uvs[face][0].select = True;
bpy.ops.uv.select_linked()#Extend selection
#Collect faces
faces_island = [face];
for f in faces_unparsed:
if f != face and f.select and f.loops[0][uv_layers].select:
print("append "+str(f.index))
faces_island.append(f)
for f in faces_island:
faces_unparsed.remove(f)
#Assign Faces to island
faces_islands[face] = faces_island
print("Sets: {}x".format(len(faces_islands)))
# Align each island to its edges
for face in faces_islands:
align_island(face_uvs[face][0].uv, face_uvs[face][1].uv, faces_islands[face])
print("Executing operator_island_align_edge")
bm = bmesh.from_edit_mesh(bpy.context.active_object.data)
uv_layers = bm.loops.layers.uv.verify()
faces_selected = [];
for face in bm.faces:
if face.select:
for loop in face.loops:
if loop[uv_layers].select:
faces_selected.append(face)
break
print("faces_selected: "+str(len(faces_selected)))
# Collect 2 uv verts for each island
face_uvs = {}
for face in faces_selected:
uvs = []
for loop in face.loops:
if loop[uv_layers].select:
uvs.append(loop[uv_layers])
if len(uvs) >= 2:
break
if len(uvs) >= 2:
face_uvs[face] = uvs
faces_islands = {}
faces_unparsed = faces_selected.copy()
for face in face_uvs:
if face in faces_unparsed:
bpy.ops.uv.select_all(action='DESELECT')
face_uvs[face][0].select = True;
bpy.ops.uv.select_linked()#Extend selection
#Collect faces
faces_island = [face];
for f in faces_unparsed:
if f != face and f.select and f.loops[0][uv_layers].select:
print("append "+str(f.index))
faces_island.append(f)
for f in faces_island:
faces_unparsed.remove(f)
#Assign Faces to island
faces_islands[face] = faces_island
print("Sets: {}x".format(len(faces_islands)))
# Align each island to its edges
for face in faces_islands:
align_island(face_uvs[face][0].uv, face_uvs[face][1].uv, faces_islands[face])
def align_island(uv_vert0, uv_vert1, faces):
bm = bmesh.from_edit_mesh(bpy.context.active_object.data)
uv_layers = bm.loops.layers.uv.verify()
bm = bmesh.from_edit_mesh(bpy.context.active_object.data)
uv_layers = bm.loops.layers.uv.verify()
print("Align {}x faces".format(len(faces)))
print("Align {}x faces".format(len(faces)))
# Select faces
bpy.ops.uv.select_all(action='DESELECT')
for face in faces:
for loop in face.loops:
loop[uv_layers].select = True
# Select faces
bpy.ops.uv.select_all(action='DESELECT')
for face in faces:
for loop in face.loops:
loop[uv_layers].select = True
diff = uv_vert1 - uv_vert0
angle = math.atan2(diff.y, diff.x)%(math.pi/2)
diff = uv_vert1 - uv_vert0
angle = math.atan2(diff.y, diff.x)%(math.pi/2)
bpy.ops.uv.select_linked()
bpy.ops.uv.select_linked()
bpy.context.tool_settings.transform_pivot_point = 'CURSOR'
bpy.ops.uv.cursor_set(location=uv_vert0 + diff/2)
bpy.context.tool_settings.transform_pivot_point = 'CURSOR'
bpy.ops.uv.cursor_set(location=uv_vert0 + diff/2)
if angle >= (math.pi/4):
angle = angle - (math.pi/2)
if angle >= (math.pi/4):
angle = angle - (math.pi/2)
bpy.ops.transform.rotate(value=angle, orient_axis='Z', constraint_axis=(False, False, False), orient_type='GLOBAL', mirror=False, use_proportional_edit=False)
bpy.ops.transform.rotate(value=angle, orient_axis='Z', constraint_axis=(False, False, False), orient_type='GLOBAL', mirror=False, use_proportional_edit=False)
bpy.utils.register_class(op)

@ -13,159 +13,159 @@ imp.reload(utilities_uv)
class op(bpy.types.Operator):
bl_idname = "uv.textools_island_align_sort"
bl_label = "Align & Sort"
bl_description = "Rotates UV islands to minimal bounds and sorts them horizontal or vertical"
bl_options = {'REGISTER', 'UNDO'}
bl_idname = "uv.textools_island_align_sort"
bl_label = "Align & Sort"
bl_description = "Rotates UV islands to minimal bounds and sorts them horizontal or vertical"
bl_options = {'REGISTER', 'UNDO'}
is_vertical : bpy.props.BoolProperty(description="Vertical or Horizontal orientation", default=True)
padding : bpy.props.FloatProperty(description="Padding between UV islands", default=0.05)
is_vertical : bpy.props.BoolProperty(description="Vertical or Horizontal orientation", default=True)
padding : bpy.props.FloatProperty(description="Padding between UV islands", default=0.05)
@classmethod
def poll(cls, context):
@classmethod
def poll(cls, context):
if not bpy.context.active_object:
return False
if not bpy.context.active_object:
return False
if bpy.context.active_object.type != 'MESH':
return False
if bpy.context.active_object.type != 'MESH':
return False
#Only in Edit mode
if bpy.context.active_object.mode != 'EDIT':
return False
#Only in Edit mode
if bpy.context.active_object.mode != 'EDIT':
return False
#Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR':
return False
#Requires UV map
if not bpy.context.object.data.uv_layers:
return False #self.report({'WARNING'}, "Object must have more than one UV map")
#Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR':
return False
#Requires UV map
if not bpy.context.object.data.uv_layers:
return False #self.report({'WARNING'}, "Object must have more than one UV map")
#Not in Synced mode
if bpy.context.scene.tool_settings.use_uv_select_sync:
return False
#Not in Synced mode
if bpy.context.scene.tool_settings.use_uv_select_sync:
return False
return True
return True
def execute(self, context):
main(context, self.is_vertical, self.padding)
return {'FINISHED'}
def execute(self, context):
main(context, self.is_vertical, self.padding)
return {'FINISHED'}
def main(context, isVertical, padding):
print("Executing IslandsAlignSort main {}".format(padding))
#Store selection
utilities_uv.selection_store()
print("Executing IslandsAlignSort main {}".format(padding))
#Store selection
utilities_uv.selection_store()
if bpy.context.tool_settings.transform_pivot_point != 'CURSOR':
bpy.context.tool_settings.transform_pivot_point = 'CURSOR'
if bpy.context.tool_settings.transform_pivot_point != 'CURSOR':
bpy.context.tool_settings.transform_pivot_point = 'CURSOR'
#Only in Face or Island mode
if bpy.context.scene.tool_settings.uv_select_mode is not 'FACE' or 'ISLAND':
bpy.context.scene.tool_settings.uv_select_mode = 'FACE'
#Only in Face or Island mode
if bpy.context.scene.tool_settings.uv_select_mode is not 'FACE' or 'ISLAND':
bpy.context.scene.tool_settings.uv_select_mode = 'FACE'
bm = bmesh.from_edit_mesh(bpy.context.active_object.data);
uv_layers = bm.loops.layers.uv.verify();
bm = bmesh.from_edit_mesh(bpy.context.active_object.data);
uv_layers = bm.loops.layers.uv.verify();
boundsAll = utilities_uv.getSelectionBBox()
boundsAll = utilities_uv.getSelectionBBox()
islands = utilities_uv.getSelectionIslands()
allSizes = {} #https://stackoverflow.com/questions/613183/sort-a-python-dictionary-by-value
allBounds = {}
islands = utilities_uv.getSelectionIslands()
allSizes = {} #https://stackoverflow.com/questions/613183/sort-a-python-dictionary-by-value
allBounds = {}
print("Islands: "+str(len(islands))+"x")
print("Islands: "+str(len(islands))+"x")
bpy.context.window_manager.progress_begin(0, len(islands))
bpy.context.window_manager.progress_begin(0, len(islands))
#Rotate to minimal bounds
for i in range(0, len(islands)):
alignIslandMinimalBounds(uv_layers, islands[i])
#Rotate to minimal bounds
for i in range(0, len(islands)):
alignIslandMinimalBounds(uv_layers, islands[i])
# Collect BBox sizes
bounds = utilities_uv.getSelectionBBox()
allSizes[i] = max(bounds['width'], bounds['height']) + i*0.000001;#Make each size unique
allBounds[i] = bounds;
print("Rotate compact: "+str(allSizes[i]))
# Collect BBox sizes
bounds = utilities_uv.getSelectionBBox()
allSizes[i] = max(bounds['width'], bounds['height']) + i*0.000001;#Make each size unique
allBounds[i] = bounds;
print("Rotate compact: "+str(allSizes[i]))
bpy.context.window_manager.progress_update(i)
bpy.context.window_manager.progress_update(i)
bpy.context.window_manager.progress_end()
bpy.context.window_manager.progress_end()
#Position by sorted size in row
sortedSizes = sorted(allSizes.items(), key=operator.itemgetter(1))#Sort by values, store tuples
sortedSizes.reverse()
offset = 0.0
for sortedSize in sortedSizes:
index = sortedSize[0]
island = islands[index]
bounds = allBounds[index]
#Position by sorted size in row
sortedSizes = sorted(allSizes.items(), key=operator.itemgetter(1))#Sort by values, store tuples
sortedSizes.reverse()
offset = 0.0
for sortedSize in sortedSizes:
index = sortedSize[0]
island = islands[index]
bounds = allBounds[index]
#Select Island
bpy.ops.uv.select_all(action='DESELECT')
utilities_uv.set_selected_faces(island)
#Offset Island
if(isVertical):
delta = Vector((boundsAll['min'].x - bounds['min'].x, boundsAll['max'].y - bounds['max'].y));
bpy.ops.transform.translate(value=(delta.x, delta.y-offset, 0))
offset += bounds['height']+padding
else:
print("Horizontal")
delta = Vector((boundsAll['min'].x - bounds['min'].x, boundsAll['max'].y - bounds['max'].y));
bpy.ops.transform.translate(value=(delta.x+offset, delta.y, 0))
offset += bounds['width']+padding
#Select Island
bpy.ops.uv.select_all(action='DESELECT')
utilities_uv.set_selected_faces(island)
#Offset Island
if(isVertical):
delta = Vector((boundsAll['min'].x - bounds['min'].x, boundsAll['max'].y - bounds['max'].y));
bpy.ops.transform.translate(value=(delta.x, delta.y-offset, 0))
offset += bounds['height']+padding
else:
print("Horizontal")
delta = Vector((boundsAll['min'].x - bounds['min'].x, boundsAll['max'].y - bounds['max'].y));
bpy.ops.transform.translate(value=(delta.x+offset, delta.y, 0))
offset += bounds['width']+padding
#Restore selection
utilities_uv.selection_restore()
#Restore selection
utilities_uv.selection_restore()
def alignIslandMinimalBounds(uv_layers, faces):
# Select Island
bpy.ops.uv.select_all(action='DESELECT')
utilities_uv.set_selected_faces(faces)
steps = 8
angle = 45; # Starting Angle, half each step
bboxPrevious = utilities_uv.getSelectionBBox()
for i in range(0, steps):
# Rotate right
bpy.ops.transform.rotate(value=(angle * math.pi / 180), orient_axis='Z')
bbox = utilities_uv.getSelectionBBox()
if i == 0:
sizeA = bboxPrevious['width'] * bboxPrevious['height']
sizeB = bbox['width'] * bbox['height']
if abs(bbox['width'] - bbox['height']) <= 0.0001 and sizeA < sizeB:
# print("Already squared")
bpy.ops.transform.rotate(value=(-angle * math.pi / 180), orient_axis='Z')
break;
if bbox['minLength'] < bboxPrevious['minLength']:
bboxPrevious = bbox; # Success
else:
# Rotate Left
bpy.ops.transform.rotate(value=(-angle*2 * math.pi / 180), orient_axis='Z')
bbox = utilities_uv.getSelectionBBox()
if bbox['minLength'] < bboxPrevious['minLength']:
bboxPrevious = bbox; # Success
else:
# Restore angle of this iteration
bpy.ops.transform.rotate(value=(angle * math.pi / 180), orient_axis='Z')
angle = angle / 2
if bboxPrevious['width'] < bboxPrevious['height']:
bpy.ops.transform.rotate(value=(90 * math.pi / 180), orient_axis='Z')
# Select Island
bpy.ops.uv.select_all(action='DESELECT')
utilities_uv.set_selected_faces(faces)
steps = 8
angle = 45; # Starting Angle, half each step
bboxPrevious = utilities_uv.getSelectionBBox()
for i in range(0, steps):
# Rotate right
bpy.ops.transform.rotate(value=(angle * math.pi / 180), orient_axis='Z')
bbox = utilities_uv.getSelectionBBox()
if i == 0:
sizeA = bboxPrevious['width'] * bboxPrevious['height']
sizeB = bbox['width'] * bbox['height']
if abs(bbox['width'] - bbox['height']) <= 0.0001 and sizeA < sizeB:
# print("Already squared")
bpy.ops.transform.rotate(value=(-angle * math.pi / 180), orient_axis='Z')
break;
if bbox['minLength'] < bboxPrevious['minLength']:
bboxPrevious = bbox; # Success
else:
# Rotate Left
bpy.ops.transform.rotate(value=(-angle*2 * math.pi / 180), orient_axis='Z')
bbox = utilities_uv.getSelectionBBox()
if bbox['minLength'] < bboxPrevious['minLength']:
bboxPrevious = bbox; # Success
else:
# Restore angle of this iteration
bpy.ops.transform.rotate(value=(angle * math.pi / 180), orient_axis='Z')
angle = angle / 2
if bboxPrevious['width'] < bboxPrevious['height']:
bpy.ops.transform.rotate(value=(90 * math.pi / 180), orient_axis='Z')
bpy.utils.register_class(op)

@ -14,278 +14,278 @@ from . import utilities_uv
class op(bpy.types.Operator):
bl_idname = "uv.textools_island_align_world"
bl_label = "Align World"
bl_description = "Align selected UV islands to world / gravity directions"
bl_options = {'REGISTER', 'UNDO'}
bl_idname = "uv.textools_island_align_world"
bl_label = "Align World"
bl_description = "Align selected UV islands to world / gravity directions"
bl_options = {'REGISTER', 'UNDO'}
# is_global = bpy.props.BoolProperty(
# name = "Global Axis",
# description = "Global or local object axis alignment",
# default = False
# )
# is_global = bpy.props.BoolProperty(
# name = "Global Axis",
# description = "Global or local object axis alignment",
# default = False
# )
# def draw(self, context):
# layout = self.layout
# layout.prop(self, "is_global")
# def draw(self, context):
# layout = self.layout
# layout.prop(self, "is_global")
@classmethod
def poll(cls, context):
if not bpy.context.active_object:
return False
@classmethod
def poll(cls, context):
if not bpy.context.active_object:
return False
#Only in Edit mode
if bpy.context.active_object.mode != 'EDIT':
return False
#Only in Edit mode
if bpy.context.active_object.mode != 'EDIT':
return False
#Requires UV map
if not bpy.context.object.data.uv_layers:
return False
#Requires UV map
if not bpy.context.object.data.uv_layers:
return False
if bpy.context.scene.tool_settings.use_uv_select_sync:
return False
if bpy.context.scene.tool_settings.use_uv_select_sync:
return False
#Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR':
return False
#Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR':
return False
return True
return True
def execute(self, context):
main(self)
return {'FINISHED'}
def execute(self, context):
main(self)
return {'FINISHED'}
def main(context):
print("\n________________________\nis_global")
print("\n________________________\nis_global")
#Store selection
utilities_uv.selection_store()
#Store selection
utilities_uv.selection_store()
bm = bmesh.from_edit_mesh(bpy.context.active_object.data)
uv_layers = bm.loops.layers.uv.verify()
bm = bmesh.from_edit_mesh(bpy.context.active_object.data)
uv_layers = bm.loops.layers.uv.verify()
#Only in Face or Island mode
if bpy.context.scene.tool_settings.uv_select_mode is not 'FACE' or 'ISLAND':
bpy.context.scene.tool_settings.uv_select_mode = 'FACE'
#Only in Face or Island mode
if bpy.context.scene.tool_settings.uv_select_mode is not 'FACE' or 'ISLAND':
bpy.context.scene.tool_settings.uv_select_mode = 'FACE'
obj = bpy.context.object
bm = bmesh.from_edit_mesh(bpy.context.active_object.data);
uv_layers = bm.loops.layers.uv.verify();
islands = utilities_uv.getSelectionIslands()
obj = bpy.context.object
bm = bmesh.from_edit_mesh(bpy.context.active_object.data);
uv_layers = bm.loops.layers.uv.verify();
islands = utilities_uv.getSelectionIslands()
for faces in islands:
# Get average viewport normal of UV island
avg_normal = Vector((0,0,0))
for face in faces:
avg_normal+=face.normal
avg_normal/=len(faces)
for faces in islands:
# Get average viewport normal of UV island
avg_normal = Vector((0,0,0))
for face in faces:
avg_normal+=face.normal
avg_normal/=len(faces)
# avg_normal = (obj.matrix_world*avg_normal).normalized()
# avg_normal = (obj.matrix_world*avg_normal).normalized()
# Which Side
x = 0
y = 1
z = 2
max_size = max(abs(avg_normal.x), abs(avg_normal.y), abs(avg_normal.z))
# Use multiple steps
for i in range(3):
if(abs(avg_normal.x) == max_size):
print("x normal")
align_island(obj, bm, uv_layers, faces, y, z, avg_normal.x < 0, False)
# Which Side
x = 0
y = 1
z = 2
max_size = max(abs(avg_normal.x), abs(avg_normal.y), abs(avg_normal.z))
# Use multiple steps
for i in range(3):
if(abs(avg_normal.x) == max_size):
print("x normal")
align_island(obj, bm, uv_layers, faces, y, z, avg_normal.x < 0, False)
elif(abs(avg_normal.y) == max_size):
print("y normal")
align_island(obj, bm, uv_layers, faces, x, z, avg_normal.y > 0, False)
elif(abs(avg_normal.y) == max_size):
print("y normal")
align_island(obj, bm, uv_layers, faces, x, z, avg_normal.y > 0, False)
elif(abs(avg_normal.z) == max_size):
print("z normal")
align_island(obj, bm, uv_layers, faces, x, y, False, avg_normal.z < 0)
elif(abs(avg_normal.z) == max_size):
print("z normal")
align_island(obj, bm, uv_layers, faces, x, y, False, avg_normal.z < 0)
print("align island: faces {}x n:{}, max:{}".format(len(faces), avg_normal, max_size))
print("align island: faces {}x n:{}, max:{}".format(len(faces), avg_normal, max_size))
#Restore selection
utilities_uv.selection_restore()
#Restore selection
utilities_uv.selection_restore()
def align_island(obj, bm, uv_layers, faces, x=0, y=1, flip_x=False, flip_y=False):
# Find lowest and highest verts
minmax_val = [0,0]
minmax_vert = [None, None]
axis_names = ['x', 'y', 'z']
print("Align shell {}x at {},{} flip {},{}".format(len(faces), axis_names[x], axis_names[y], flip_x, flip_y))
# print(" Min #{} , Max #{} along '{}'".format(minmax_vert[0].index, minmax_vert[1].index, axis_names[y] ))
# print(" A1 {:.1f} , A2 {:.1f} along ".format(minmax_val[0], minmax_val[1] ))
# Find lowest and highest verts
minmax_val = [0,0]
minmax_vert = [None, None]
axis_names = ['x', 'y', 'z']
print("Align shell {}x at {},{} flip {},{}".format(len(faces), axis_names[x], axis_names[y], flip_x, flip_y))
# print(" Min #{} , Max #{} along '{}'".format(minmax_vert[0].index, minmax_vert[1].index, axis_names[y] ))
# print(" A1 {:.1f} , A2 {:.1f} along ".format(minmax_val[0], minmax_val[1] ))
# Collect UV to Vert
vert_to_uv = utilities_uv.get_vert_to_uv(bm, uv_layers)
uv_to_vert = utilities_uv.get_uv_to_vert(bm, uv_layers)
processed_edges = []
edges = []
for face in faces:
for edge in face.edges:
if edge not in processed_edges:
processed_edges.append(edge)
delta = edge.verts[0].co -edge.verts[1].co
max_side = max(abs(delta.x), abs(delta.y), abs(delta.z))
# Check edges dominant in active axis
if( abs(delta[x]) == max_side or abs(delta[y]) == max_side):
# if( abs(delta[y]) == max_side):
edges.append(edge)
print("Edges {}x".format(len(edges)))
avg_angle = 0
for edge in edges:
uv0 = vert_to_uv[ edge.verts[0] ][0]
uv1 = vert_to_uv[ edge.verts[1] ][0]
delta_verts = Vector((
edge.verts[1].co[x] - edge.verts[0].co[x],
edge.verts[1].co[y] - edge.verts[0].co[y]
))
if flip_x:
delta_verts.x = -edge.verts[1].co[x] + edge.verts[0].co[x]
if flip_y:
delta_verts.y = -edge.verts[1].co[y] + edge.verts[0].co[y]
# delta_verts.y = edge.verts[0].co[y] - edge.verts[1].co[y]
delta_uvs = Vector((
uv1.uv.x - uv0.uv.x,
uv1.uv.y - uv0.uv.y
))
a0 = math.atan2(delta_verts.y, delta_verts.x) - math.pi/2
a1 = math.atan2(delta_uvs.y, delta_uvs.x) - math.pi/2
a_delta = math.atan2(math.sin(a0-a1), math.cos(a0-a1))
# edge.verts[0].index, edge.verts[1].index
# print(" turn {:.1f} .. {:.1f} , {:.1f}".format(a_delta*180/math.pi, a0*180/math.pi,a1*180/math.pi))
avg_angle+=a_delta
avg_angle/=len(edges) # - math.pi/2
print("Turn {:.1f}".format(avg_angle * 180/math.pi))
bpy.ops.uv.select_all(action='DESELECT')
for face in faces:
for loop in face.loops:
loop[uv_layers].select = True
bpy.context.tool_settings.transform_pivot_point = 'MEDIAN_POINT'
bpy.ops.transform.rotate(value=avg_angle, orient_axis='Z')
# bpy.ops.transform.rotate(value=0.58191, axis=(-0, -0, -1), constraint_axis=(False, False, False), orient_type='GLOBAL', mirror=False, use_proportional_edit=False, proportional_edit_falloff='SPHERE', proportional_size=0.0267348)
# processed = []
'''
bpy.ops.uv.select_all(action='DESELECT')
for face in faces:
# Collect UV to Vert
for loop in face.loops:
loop[uv_layers].select = True
vert = loop.vert
uv = loop[uv_layers]
# vert_to_uv
if vert not in vert_to_uv:
vert_to_uv[vert] = [uv];
else:
vert_to_uv[vert].append(uv)
# uv_to_vert
if uv not in uv_to_vert:
uv_to_vert[ uv ] = vert;
for vert in face.verts:
if vert not in processed:
processed.append(vert)
vert_y = (vert.co)[y] #obj.matrix_world *
print("idx {} = {}".format(vert.index, vert_y))
if not minmax_vert[0] or not minmax_vert[1]:
minmax_vert[0] = vert
minmax_vert[1] = vert
minmax_val[0] = vert_y
minmax_val[1] = vert_y
continue
if vert_y < minmax_val[0]:
# Not yet defined or smaller
minmax_vert[0] = vert
minmax_val[0] = vert_y
elif vert_y > minmax_val[1]:
minmax_vert[1] = vert
minmax_val[1] = vert_y
if minmax_vert[0] and minmax_vert[1]:
axis_names = ['x', 'y', 'z']
print(" Min #{} , Max #{} along '{}'".format(minmax_vert[0].index, minmax_vert[1].index, axis_names[y] ))
# print(" A1 {:.1f} , A2 {:.1f} along ".format(minmax_val[0], minmax_val[1] ))
vert_A = minmax_vert[0]
vert_B = minmax_vert[1]
uv_A = vert_to_uv[vert_A][0]
uv_B = vert_to_uv[vert_B][0]
delta_verts = Vector((
vert_B.co[x] - vert_A.co[x],
vert_B.co[y] - vert_A.co[y]
))
delta_uvs = Vector((
uv_B.uv.x - uv_A.uv.x,
uv_B.uv.y - uv_A.uv.y,
))
# Get angles
angle_vert = math.atan2(delta_verts.y, delta_verts.x) - math.pi/2
angle_uv = math.atan2(delta_uvs.y, delta_uvs.x) - math.pi/2
angle_delta = math.atan2(math.sin(angle_vert-angle_uv), math.cos(angle_vert-angle_uv))
print(" Angles {:.2f} | {:.2f}".format(angle_vert*180/math.pi, angle_uv*180/math.pi))
print(" Angle Diff {:.2f}".format(angle_delta*180/math.pi))
bpy.context.tool_settings.transform_pivot_point = 'MEDIAN_POINT'
bpy.ops.transform.rotate(value=angle_delta, axis='Z')
# bpy.ops.transform.rotate(value=0.58191, axis=(-0, -0, -1), constraint_axis=(False, False, False), orient_type='GLOBAL', mirror=False, use_proportional_edit=False, proportional_edit_falloff='SPHERE', proportional_size=0.0267348)
# bpy.ops.mesh.select_all(action='DESELECT')
# vert_A.select = True
# vert_B.select = True
# return
'''
# Collect UV to Vert
vert_to_uv = utilities_uv.get_vert_to_uv(bm, uv_layers)
uv_to_vert = utilities_uv.get_uv_to_vert(bm, uv_layers)
processed_edges = []
edges = []
for face in faces:
for edge in face.edges:
if edge not in processed_edges:
processed_edges.append(edge)
delta = edge.verts[0].co -edge.verts[1].co
max_side = max(abs(delta.x), abs(delta.y), abs(delta.z))
# Check edges dominant in active axis
if( abs(delta[x]) == max_side or abs(delta[y]) == max_side):
# if( abs(delta[y]) == max_side):
edges.append(edge)
print("Edges {}x".format(len(edges)))
avg_angle = 0
for edge in edges:
uv0 = vert_to_uv[ edge.verts[0] ][0]
uv1 = vert_to_uv[ edge.verts[1] ][0]
delta_verts = Vector((
edge.verts[1].co[x] - edge.verts[0].co[x],
edge.verts[1].co[y] - edge.verts[0].co[y]
))
if flip_x:
delta_verts.x = -edge.verts[1].co[x] + edge.verts[0].co[x]
if flip_y:
delta_verts.y = -edge.verts[1].co[y] + edge.verts[0].co[y]
# delta_verts.y = edge.verts[0].co[y] - edge.verts[1].co[y]
delta_uvs = Vector((
uv1.uv.x - uv0.uv.x,
uv1.uv.y - uv0.uv.y
))
a0 = math.atan2(delta_verts.y, delta_verts.x) - math.pi/2
a1 = math.atan2(delta_uvs.y, delta_uvs.x) - math.pi/2
a_delta = math.atan2(math.sin(a0-a1), math.cos(a0-a1))
# edge.verts[0].index, edge.verts[1].index
# print(" turn {:.1f} .. {:.1f} , {:.1f}".format(a_delta*180/math.pi, a0*180/math.pi,a1*180/math.pi))
avg_angle+=a_delta
avg_angle/=len(edges) # - math.pi/2
print("Turn {:.1f}".format(avg_angle * 180/math.pi))
bpy.ops.uv.select_all(action='DESELECT')
for face in faces:
for loop in face.loops:
loop[uv_layers].select = True
bpy.context.tool_settings.transform_pivot_point = 'MEDIAN_POINT'
bpy.ops.transform.rotate(value=avg_angle, orient_axis='Z')
# bpy.ops.transform.rotate(value=0.58191, axis=(-0, -0, -1), constraint_axis=(False, False, False), orient_type='GLOBAL', mirror=False, use_proportional_edit=False, proportional_edit_falloff='SPHERE', proportional_size=0.0267348)
# processed = []
'''
bpy.ops.uv.select_all(action='DESELECT')
for face in faces:
# Collect UV to Vert
for loop in face.loops:
loop[uv_layers].select = True
vert = loop.vert
uv = loop[uv_layers]
# vert_to_uv
if vert not in vert_to_uv:
vert_to_uv[vert] = [uv];
else:
vert_to_uv[vert].append(uv)
# uv_to_vert
if uv not in uv_to_vert:
uv_to_vert[ uv ] = vert;
for vert in face.verts:
if vert not in processed:
processed.append(vert)
vert_y = (vert.co)[y] #obj.matrix_world *
print("idx {} = {}".format(vert.index, vert_y))
if not minmax_vert[0] or not minmax_vert[1]:
minmax_vert[0] = vert
minmax_vert[1] = vert
minmax_val[0] = vert_y
minmax_val[1] = vert_y
continue
if vert_y < minmax_val[0]:
# Not yet defined or smaller
minmax_vert[0] = vert
minmax_val[0] = vert_y
elif vert_y > minmax_val[1]:
minmax_vert[1] = vert
minmax_val[1] = vert_y
if minmax_vert[0] and minmax_vert[1]:
axis_names = ['x', 'y', 'z']
print(" Min #{} , Max #{} along '{}'".format(minmax_vert[0].index, minmax_vert[1].index, axis_names[y] ))
# print(" A1 {:.1f} , A2 {:.1f} along ".format(minmax_val[0], minmax_val[1] ))
vert_A = minmax_vert[0]
vert_B = minmax_vert[1]
uv_A = vert_to_uv[vert_A][0]
uv_B = vert_to_uv[vert_B][0]
delta_verts = Vector((
vert_B.co[x] - vert_A.co[x],
vert_B.co[y] - vert_A.co[y]
))
delta_uvs = Vector((
uv_B.uv.x - uv_A.uv.x,
uv_B.uv.y - uv_A.uv.y,
))
# Get angles
angle_vert = math.atan2(delta_verts.y, delta_verts.x) - math.pi/2
angle_uv = math.atan2(delta_uvs.y, delta_uvs.x) - math.pi/2
angle_delta = math.atan2(math.sin(angle_vert-angle_uv), math.cos(angle_vert-angle_uv))
print(" Angles {:.2f} | {:.2f}".format(angle_vert*180/math.pi, angle_uv*180/math.pi))
print(" Angle Diff {:.2f}".format(angle_delta*180/math.pi))
bpy.context.tool_settings.transform_pivot_point = 'MEDIAN_POINT'
bpy.ops.transform.rotate(value=angle_delta, axis='Z')
# bpy.ops.transform.rotate(value=0.58191, axis=(-0, -0, -1), constraint_axis=(False, False, False), orient_type='GLOBAL', mirror=False, use_proportional_edit=False, proportional_edit_falloff='SPHERE', proportional_size=0.0267348)
# bpy.ops.mesh.select_all(action='DESELECT')
# vert_A.select = True
# vert_B.select = True
# return
'''
bpy.utils.register_class(op)

File diff suppressed because it is too large Load Diff

@ -9,73 +9,73 @@ from math import pi
from . import utilities_uv
class op(bpy.types.Operator):
bl_idname = "uv.textools_island_rotate_90"
bl_label = "Rotate 90 degrees"
bl_description = "Rotate the selected UV island 90 degrees left or right"
bl_options = {'REGISTER', 'UNDO'}
angle : bpy.props.FloatProperty(name="Angle")
bl_idname = "uv.textools_island_rotate_90"
bl_label = "Rotate 90 degrees"
bl_description = "Rotate the selected UV island 90 degrees left or right"
bl_options = {'REGISTER', 'UNDO'}
angle : bpy.props.FloatProperty(name="Angle")
@classmethod
def poll(cls, context):
#Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR':
return False
@classmethod
def poll(cls, context):
#Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR':
return False
if not bpy.context.active_object:
return False
if not bpy.context.active_object:
return False
if bpy.context.active_object.type != 'MESH':
return False
if bpy.context.active_object.type != 'MESH':
return False
#Only in Edit mode
if bpy.context.active_object.mode != 'EDIT':
return False
#Only in Edit mode
if bpy.context.active_object.mode != 'EDIT':
return False
#Requires UV map
if not bpy.context.object.data.uv_layers:
return False
# Not in Synced mode
if bpy.context.scene.tool_settings.use_uv_select_sync:
return False
#Requires UV map
if not bpy.context.object.data.uv_layers:
return False
# Not in Synced mode
if bpy.context.scene.tool_settings.use_uv_select_sync:
return False
return True
return True
def execute(self, context):
def execute(self, context):
main(context, self.angle)
return {'FINISHED'}
main(context, self.angle)
return {'FINISHED'}
def main(context, angle):
#Store selection
utilities_uv.selection_store()
bm = bmesh.from_edit_mesh(bpy.context.active_object.data)
uv_layers = bm.loops.layers.uv.verify()
bpy.ops.uv.select_linked()
#Bounds
bounds_initial = utilities_uv.getSelectionBBox()
bpy.ops.transform.rotate(value=angle, orient_axis='Z', constraint_axis=(False, False, False), use_proportional_edit=False)
#Align rotation to top left|right
bounds_post = utilities_uv.getSelectionBBox()
dy = bounds_post['max'].y - bounds_initial['max'].y
dx = 0
if angle > 0:
dx = bounds_post['max'].x - bounds_initial['max'].x
else:
dx = bounds_post['min'].x - bounds_initial['min'].x
bpy.ops.transform.translate(value=(-dx, -dy, 0), constraint_axis=(False, False, False), use_proportional_edit=False)
#Restore selection
utilities_uv.selection_restore()
#Store selection
utilities_uv.selection_store()
bm = bmesh.from_edit_mesh(bpy.context.active_object.data)
uv_layers = bm.loops.layers.uv.verify()
bpy.ops.uv.select_linked()
#Bounds
bounds_initial = utilities_uv.getSelectionBBox()
bpy.ops.transform.rotate(value=angle, orient_axis='Z', constraint_axis=(False, False, False), use_proportional_edit=False)
#Align rotation to top left|right
bounds_post = utilities_uv.getSelectionBBox()
dy = bounds_post['max'].y - bounds_initial['max'].y
dx = 0
if angle > 0:
dx = bounds_post['max'].x - bounds_initial['max'].x
else:
dx = bounds_post['min'].x - bounds_initial['min'].x
bpy.ops.transform.translate(value=(-dx, -dy, 0), constraint_axis=(False, False, False), use_proportional_edit=False)
#Restore selection
utilities_uv.selection_restore()
bpy.utils.register_class(op)

@ -9,216 +9,216 @@ from math import pi
from . import utilities_uv
class op(bpy.types.Operator):
bl_idname = "uv.textools_island_straighten_edge_loops"
bl_label = "Straight edge loops"
bl_description = "Straighten edge loops of UV Island and relax rest"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
bl_idname = "uv.textools_island_straighten_edge_loops"
bl_label = "Straight edge loops"
bl_description = "Straighten edge loops of UV Island and relax rest"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
if not bpy.context.active_object:
return False
if not bpy.context.active_object:
return False
if bpy.context.active_object.type != 'MESH':
return False
if bpy.context.active_object.type != 'MESH':
return False
#Only in Edit mode
if bpy.context.active_object.mode != 'EDIT':
return False
#Only in Edit mode
if bpy.context.active_object.mode != 'EDIT':
return False
#Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR':
return False
#Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR':
return False
#Requires UV map
if not bpy.context.object.data.uv_layers:
return False
#Requires UV map
if not bpy.context.object.data.uv_layers:
return False
if bpy.context.scene.tool_settings.uv_select_mode != 'EDGE':
return False
if bpy.context.scene.tool_settings.uv_select_mode != 'EDGE':
return False
return True
return True
def execute(self, context):
def execute(self, context):
main(context)
return {'FINISHED'}
main(context)
return {'FINISHED'}
def main(context):
print("____________________________")
#Store selection
utilities_uv.selection_store()
print("____________________________")
#Store selection
utilities_uv.selection_store()
bm = bmesh.from_edit_mesh(bpy.context.active_object.data)
uv_layers = bm.loops.layers.uv.verify()
edges = utilities_uv.get_selected_uv_edges(bm, uv_layers)
islands = utilities_uv.getSelectionIslands()
uvs = utilities_uv.get_selected_uvs(bm, uv_layers)
faces = [f for island in islands for f in island ]
bm = bmesh.from_edit_mesh(bpy.context.active_object.data)
uv_layers = bm.loops.layers.uv.verify()
edges = utilities_uv.get_selected_uv_edges(bm, uv_layers)
islands = utilities_uv.getSelectionIslands()
uvs = utilities_uv.get_selected_uvs(bm, uv_layers)
faces = [f for island in islands for f in island ]
# Get island faces
# Get island faces
# utilities_uv.selection_restore(bm, uv_layers)
# utilities_uv.selection_restore(bm, uv_layers)
groups = get_edge_groups(bm, uv_layers, faces, edges, uvs)
groups = get_edge_groups(bm, uv_layers, faces, edges, uvs)
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE')
bpy.ops.mesh.select_all(action='DESELECT')
for face in faces:
face.select = True
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE')
bpy.ops.mesh.select_all(action='DESELECT')
for face in faces:
face.select = True
print("Edges {}x".format(len(edges)))
print("Groups {}x".format(len(groups)))
print("Edges {}x".format(len(edges)))
print("Groups {}x".format(len(groups)))
# Restore 3D face selection
# Restore 3D face selection
# Restore UV seams and clear pins
bpy.ops.uv.seams_from_islands()
bpy.ops.uv.pin(clear=True)
# Restore UV seams and clear pins
bpy.ops.uv.seams_from_islands()
bpy.ops.uv.pin(clear=True)
edge_sets = []
for edges in groups:
edge_sets.append( EdgeSet(bm, uv_layers, edges, faces) )
# straighten_edges(bm, uv_layers, edges, faces)
edge_sets = []
for edges in groups:
edge_sets.append( EdgeSet(bm, uv_layers, edges, faces) )
# straighten_edges(bm, uv_layers, edges, faces)
sorted_sets = sorted(edge_sets, key=lambda x: x.length, reverse=True)
sorted_sets = sorted(edge_sets, key=lambda x: x.length, reverse=True)
for edge_set in sorted_sets:
edge_set.straighten()
#Restore selection
utilities_uv.selection_restore()
for edge_set in sorted_sets:
edge_set.straighten()
#Restore selection
utilities_uv.selection_restore()
class EdgeSet:
bm = None
edges = []
faces = []
uv_layers = ''
vert_to_uv = {}
edge_length = {}
length = 0
def __init__(self, bm, uv_layers, edges, faces):
self.bm = bm
self.uv_layers = uv_layers
self.edges = edges
self.faces = faces
# Get Vert to UV within faces
self.vert_to_uv = utilities_uv.get_vert_to_uv(bm, uv_layers)
# Get edge lengths
self.edge_length = {}
self.length = 0
for e in edges:
uv1 = self.vert_to_uv[e.verts[0]][0].uv
uv2 = self.vert_to_uv[e.verts[1]][0].uv
self.edge_length[e] = (uv2 - uv1).length
self.length+=self.edge_length[e]
def straighten(self):
print("Straight {}x at {:.2f} length ".format(len(self.edges), self.length))
# Get edge angles in UV space
angles = {}
for edge in self.edges:
uv1 = self.vert_to_uv[edge.verts[0]][0].uv
uv2 = self.vert_to_uv[edge.verts[1]][0].uv
delta = uv2 - uv1
angle = math.atan2(delta.y, delta.x)%(math.pi/2)
if angle >= (math.pi/4):
angle = angle - (math.pi/2)
angles[edge] = abs(angle)
# print("Angle {:.2f} degr".format(angle * 180 / math.pi))
# Pick edge with least rotation offset to U or V axis
edge_main = sorted(angles.items(), key = operator.itemgetter(1))[0][0]
print("Main edge: {} at {:.2f} degr".format( edge_main.index, angles[edge_main] * 180 / math.pi ))
# Rotate main edge to closest axis
uvs = [uv for v in edge_main.verts for uv in self.vert_to_uv[v]]
bpy.ops.uv.select_all(action='DESELECT')
for uv in uvs:
uv.select = True
uv1 = self.vert_to_uv[edge_main.verts[0]][0].uv
uv2 = self.vert_to_uv[edge_main.verts[1]][0].uv
diff = uv2 - uv1
angle = math.atan2(diff.y, diff.x)%(math.pi/2)
if angle >= (math.pi/4):
angle = angle - (math.pi/2)
bpy.ops.uv.cursor_set(location=uv1 + diff/2)
bpy.ops.transform.rotate(value=angle, orient_axis='Z', constraint_axis=(False, False, False), orient_type='GLOBAL', mirror=False, use_proportional_edit=False)
# Expand edges and straighten
count = len(self.edges)
processed = [edge_main]
for i in range(count):
if(len(processed) < len(self.edges)):
verts = set([v for e in processed for v in e.verts])
edges_expand = [e for e in self.edges if e not in processed and (e.verts[0] in verts or e.verts[1] in verts)]
verts_ends = [v for e in edges_expand for v in e.verts if v in verts]
print("Step, proc {} exp: {}".format( [e.index for e in processed] , [e.index for e in edges_expand] ))
if len(edges_expand) == 0:
continue
for edge in edges_expand:
# if edge.verts[0] in verts_ends and edge.verts[1] in verts_ends:
# print("Cancel at edge {}".format(edge.index))
# return
print(" E {} verts {} verts end: {}".format(edge.index, [v.index for v in edge.verts], [v.index for v in verts_ends]))
v1 = [v for v in edge.verts if v in verts_ends][0]
v2 = [v for v in edge.verts if v not in verts_ends][0]
# direction
previous_edge = [e for e in processed if e.verts[0] in edge.verts or e.verts[1] in edge.verts][0]
prev_v1 = [v for v in previous_edge.verts if v != v1][0]
prev_v2 = [v for v in previous_edge.verts if v == v1][0]
direction = (self.vert_to_uv[prev_v2][0].uv - self.vert_to_uv[prev_v1][0].uv).normalized()
for uv in self.vert_to_uv[v2]:
uv.uv = self.vert_to_uv[v1][0].uv + direction * self.edge_length[edge]
print("Procesed {}x Expand {}x".format(len(processed), len(edges_expand) ))
print("verts_ends: {}x".format(len(verts_ends)))
processed.extend(edges_expand)
# Select edges
uvs = list(set( [uv for e in self.edges for v in e.verts for uv in self.vert_to_uv[v] ] ))
bpy.ops.uv.select_all(action='DESELECT')
for uv in uvs:
uv.select = True
# Pin UV's
bpy.ops.uv.pin()
bpy.ops.uv.unwrap(method='ANGLE_BASED', margin=0.001)
bpy.ops.uv.pin(clear=True)
bm = None
edges = []
faces = []
uv_layers = ''
vert_to_uv = {}
edge_length = {}
length = 0
def __init__(self, bm, uv_layers, edges, faces):
self.bm = bm
self.uv_layers = uv_layers
self.edges = edges
self.faces = faces
# Get Vert to UV within faces
self.vert_to_uv = utilities_uv.get_vert_to_uv(bm, uv_layers)
# Get edge lengths
self.edge_length = {}
self.length = 0
for e in edges:
uv1 = self.vert_to_uv[e.verts[0]][0].uv
uv2 = self.vert_to_uv[e.verts[1]][0].uv
self.edge_length[e] = (uv2 - uv1).length
self.length+=self.edge_length[e]
def straighten(self):
print("Straight {}x at {:.2f} length ".format(len(self.edges), self.length))
# Get edge angles in UV space
angles = {}
for edge in self.edges:
uv1 = self.vert_to_uv[edge.verts[0]][0].uv
uv2 = self.vert_to_uv[edge.verts[1]][0].uv
delta = uv2 - uv1
angle = math.atan2(delta.y, delta.x)%(math.pi/2)
if angle >= (math.pi/4):
angle = angle - (math.pi/2)
angles[edge] = abs(angle)
# print("Angle {:.2f} degr".format(angle * 180 / math.pi))
# Pick edge with least rotation offset to U or V axis
edge_main = sorted(angles.items(), key = operator.itemgetter(1))[0][0]
print("Main edge: {} at {:.2f} degr".format( edge_main.index, angles[edge_main] * 180 / math.pi ))
# Rotate main edge to closest axis
uvs = [uv for v in edge_main.verts for uv in self.vert_to_uv[v]]
bpy.ops.uv.select_all(action='DESELECT')
for uv in uvs:
uv.select = True
uv1 = self.vert_to_uv[edge_main.verts[0]][0].uv
uv2 = self.vert_to_uv[edge_main.verts[1]][0].uv
diff = uv2 - uv1
angle = math.atan2(diff.y, diff.x)%(math.pi/2)
if angle >= (math.pi/4):
angle = angle - (math.pi/2)
bpy.ops.uv.cursor_set(location=uv1 + diff/2)
bpy.ops.transform.rotate(value=angle, orient_axis='Z', constraint_axis=(False, False, False), orient_type='GLOBAL', mirror=False, use_proportional_edit=False)
# Expand edges and straighten
count = len(self.edges)
processed = [edge_main]
for i in range(count):
if(len(processed) < len(self.edges)):
verts = set([v for e in processed for v in e.verts])
edges_expand = [e for e in self.edges if e not in processed and (e.verts[0] in verts or e.verts[1] in verts)]
verts_ends = [v for e in edges_expand for v in e.verts if v in verts]
print("Step, proc {} exp: {}".format( [e.index for e in processed] , [e.index for e in edges_expand] ))
if len(edges_expand) == 0:
continue
for edge in edges_expand:
# if edge.verts[0] in verts_ends and edge.verts[1] in verts_ends:
# print("Cancel at edge {}".format(edge.index))
# return
print(" E {} verts {} verts end: {}".format(edge.index, [v.index for v in edge.verts], [v.index for v in verts_ends]))
v1 = [v for v in edge.verts if v in verts_ends][0]
v2 = [v for v in edge.verts if v not in verts_ends][0]
# direction
previous_edge = [e for e in processed if e.verts[0] in edge.verts or e.verts[1] in edge.verts][0]
prev_v1 = [v for v in previous_edge.verts if v != v1][0]
prev_v2 = [v for v in previous_edge.verts if v == v1][0]
direction = (self.vert_to_uv[prev_v2][0].uv - self.vert_to_uv[prev_v1][0].uv).normalized()
for uv in self.vert_to_uv[v2]:
uv.uv = self.vert_to_uv[v1][0].uv + direction * self.edge_length[edge]
print("Procesed {}x Expand {}x".format(len(processed), len(edges_expand) ))
print("verts_ends: {}x".format(len(verts_ends)))
processed.extend(edges_expand)
# Select edges
uvs = list(set( [uv for e in self.edges for v in e.verts for uv in self.vert_to_uv[v] ] ))
bpy.ops.uv.select_all(action='DESELECT')
for uv in uvs:
uv.select = True
# Pin UV's
bpy.ops.uv.pin()
bpy.ops.uv.unwrap(method='ANGLE_BASED', margin=0.001)
bpy.ops.uv.pin(clear=True)
@ -228,43 +228,43 @@ class EdgeSet:
def get_edge_groups(bm, uv_layers, faces, edges, uvs):
print("Get edge groups, edges {}x".format(len(edges))+"x")
print("Get edge groups, edges {}x".format(len(edges))+"x")
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE')
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE')
unmatched = edges.copy()
unmatched = edges.copy()
groups = []
groups = []
for edge in edges:
if edge in unmatched:
for edge in edges:
if edge in unmatched:
# Loop select edge
bpy.ops.mesh.select_all(action='DESELECT')
edge.select = True
bpy.ops.mesh.loop_multi_select(ring=False)
# Loop select edge
bpy.ops.mesh.select_all(action='DESELECT')
edge.select = True
bpy.ops.mesh.loop_multi_select(ring=False)
# Isolate group within edges
group = [e for e in bm.edges if e.select and e in edges]
groups.append(group)
# Isolate group within edges
group = [e for e in bm.edges if e.select and e in edges]
groups.append(group)
# Remove from unmatched
for e in group:
if e in unmatched:
unmatched.remove(e)
# Remove from unmatched
for e in group:
if e in unmatched:
unmatched.remove(e)
print(" Edge {} : Group: {}x , unmatched: {}".format(edge.index, len(group), len(unmatched)))
print(" Edge {} : Group: {}x , unmatched: {}".format(edge.index, len(group), len(unmatched)))
# return
# group = [edge]
# for e in bm.edges:
# if e.select and e in unmatched:
# unmatched.remove(e)
# group.append(edge)
# return
# group = [edge]
# for e in bm.edges:
# if e.select and e in unmatched:
# unmatched.remove(e)
# group.append(edge)
return groups
return groups
bpy.utils.register_class(op)

@ -11,286 +11,286 @@ from . import utilities_meshtex
def get_mode():
if not utilities_meshtex.find_uv_mesh([bpy.context.active_object]):
# Create UV mesh from face selection
if bpy.context.active_object and bpy.context.active_object.mode == 'EDIT':
return 'FACES'
if not utilities_meshtex.find_uv_mesh([bpy.context.active_object]):
# Create UV mesh from face selection
if bpy.context.active_object and bpy.context.active_object.mode == 'EDIT':
return 'FACES'
# Create UV mesh from whole object
if bpy.context.active_object and bpy.context.active_object.type == 'MESH':
if "SurfaceDeform" not in bpy.context.active_object.modifiers:
return 'OBJECT'
# Create UV mesh from whole object
if bpy.context.active_object and bpy.context.active_object.type == 'MESH':
if "SurfaceDeform" not in bpy.context.active_object.modifiers:
return 'OBJECT'
return 'UNDEFINED'
return 'UNDEFINED'
class op(bpy.types.Operator):
bl_idname = "uv.textools_meshtex_create"
bl_label = "UV Mesh"
bl_description = "Create a new UV Mesh from your selected object"
bl_options = {'REGISTER', 'UNDO'}
bl_idname = "uv.textools_meshtex_create"
bl_label = "UV Mesh"
bl_description = "Create a new UV Mesh from your selected object"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
if get_mode() == 'UNDEFINED':
return False
return True
@classmethod
def poll(cls, context):
if get_mode() == 'UNDEFINED':
return False
return True
def execute(self, context):
create_uv_mesh(self, bpy.context.active_object)
return {'FINISHED'}
def execute(self, context):
create_uv_mesh(self, bpy.context.active_object)
return {'FINISHED'}
def create_uv_mesh(self, obj):
mode = bpy.context.active_object.mode
# Select
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='DESELECT')
obj.select_set( state = True, view_layer = None)
bpy.context.view_layer.objects.active = obj
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_mode(type="FACE")
bpy.context.scene.tool_settings.use_uv_select_sync = False
# Select all if OBJECT mode
if mode == 'OBJECT':
bpy.ops.mesh.select_all(action='SELECT')
# bpy.ops.uv.select_all(action='SELECT')
# Create UV Map
if not obj.data.uv_layers:
if mode == 'OBJECT':
# Smart UV project
bpy.ops.uv.smart_project(
angle_limit=65,
island_margin=0.5,
user_area_weight=0,
use_aspect=True,
stretch_to_bounds=True
)
elif mode == 'EDIT':
# Iron Faces
bpy.ops.uv.unwrap(method='ANGLE_BASED', margin=0)
bpy.ops.uv.textools_unwrap_faces_iron()
bm = bmesh.from_edit_mesh(obj.data)
uv_layers = bm.loops.layers.uv.verify()
#Collect UV islands
bpy.ops.uv.select_all(action='SELECT')
islands = utilities_uv.getSelectionIslands(bm, uv_layers)
# Collect clusters
uvs = {}
clusters = []
uv_to_clusters = {}
vert_to_clusters = {}
face_area_view = 0
face_area_uv = 0
for face in bm.faces:
if face.select:
# Calculate triangle area for UV and View
# Triangle Verts
tri_uv = [loop[uv_layers].uv for loop in face.loops ]
tri_vt = [vert.co for vert in face.verts]
#Triangle Areas
face_area_view += math.sqrt(utilities_texel.get_area_triangle(
tri_vt[0],
tri_vt[1],
tri_vt[2]
))
face_area_uv += math.sqrt(utilities_texel.get_area_triangle(
tri_uv[0],
tri_uv[1],
tri_uv[2]
))
for i in range(len(face.loops)):
v = face.loops[i]
uv = Get_UVSet(uvs, bm, uv_layers, face.index, i)
# # clusters
isMerged = False
for cluster in clusters:
d = (uv.pos() - cluster.uvs[0].pos()).length
if d <= 0.0000001:
#Merge
cluster.append(uv)
uv_to_clusters[uv] = cluster
if v not in vert_to_clusters:
vert_to_clusters[v] = cluster
isMerged = True;
break;
if not isMerged:
#New Group
clusters.append( UVCluster(v, [uv]) )
uv_to_clusters[uv] = clusters[-1]
if v not in vert_to_clusters:
vert_to_clusters[v] = clusters[-1]
scale = face_area_view / face_area_uv
print("Scale {}x {} | {}".format(scale, face_area_view, face_area_uv))
print("Islands {}x".format(len(islands)))
print("UV Vert Clusters {}x".format(len(clusters)))
m_vert_cluster = []
m_verts_org = []
m_verts_A = []
m_verts_B = []
m_faces = []
for island in islands:
for face in island:
f = []
for i in range(len(face.loops)):
v = face.loops[i].vert
uv = Get_UVSet(uvs, bm, uv_layers, face.index, i)
c = uv_to_clusters[ uv ]
index = 0
if c in m_vert_cluster:
index = m_vert_cluster.index(c)
else:
index = len(m_vert_cluster)
m_vert_cluster.append(c)
m_verts_org.append(v)
m_verts_A.append( Vector((uv.pos().x*scale - scale/2, uv.pos().y*scale -scale/2, 0)) )
m_verts_B.append( obj.matrix_world @ v.co - bpy.context.scene.cursor.location )
f.append(index)
m_faces.append(f)
# Add UV bounds as edges
verts = [
Vector((-scale/2, -scale/2, 0)),
Vector(( scale/2, -scale/2, 0)),
Vector(( scale/2, scale/2, 0)),
Vector((-scale/2, scale/2, 0)),
]
m_verts_A = m_verts_A+verts;
m_verts_B = m_verts_B+verts;
bpy.ops.object.mode_set(mode='OBJECT')
# Create Mesh
mesh = bpy.data.meshes.new("mesh_texture")
mesh.from_pydata(m_verts_A, [], m_faces)
mesh.update()
mesh_obj = bpy.data.objects.new("UV_mesh {0}".format(obj.name), mesh)
mesh_obj.location = bpy.context.scene.cursor.location
bpy.context.collection.objects.link(mesh_obj)
# Add shape keys
mesh_obj.shape_key_add(name="uv", from_mix=True)
mesh_obj.shape_key_add(name="model", from_mix=True)
mesh_obj.active_shape_key_index = 1
# Select
bpy.context.view_layer.objects.active = mesh_obj
mesh_obj.select_set( state = True, view_layer = None)
bpy.ops.object.mode_set(mode='EDIT')
bm = bmesh.from_edit_mesh(mesh_obj.data)
if hasattr(bm.faces, "ensure_lookup_table"):
bm.faces.ensure_lookup_table()
bm.verts.ensure_lookup_table()
bm.edges.new((bm.verts[-4], bm.verts[-3]))
bm.edges.new((bm.verts[-3], bm.verts[-2]))
bm.edges.new((bm.verts[-2], bm.verts[-1]))
bm.edges.new((bm.verts[-1], bm.verts[-4]))
for i in range(len(m_verts_B)):
bm.verts[i].co = m_verts_B[i]
# Split concave faces to resolve issues with Shape deform
bpy.context.object.active_shape_key_index = 0
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE')
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.mesh.vert_connect_concave()
bpy.ops.object.mode_set(mode='OBJECT')
# Display as edges only
mesh_obj.show_wire = True
mesh_obj.show_all_edges = True
# mesh_obj.data.display_type = 'WIRE' #Esta linea deberia llevarte a la opcion wireframe
mode = bpy.context.active_object.mode
# Select
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='DESELECT')
obj.select_set( state = True, view_layer = None)
bpy.context.view_layer.objects.active = obj
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_mode(type="FACE")
bpy.context.scene.tool_settings.use_uv_select_sync = False
# Select all if OBJECT mode
if mode == 'OBJECT':
bpy.ops.mesh.select_all(action='SELECT')
# bpy.ops.uv.select_all(action='SELECT')
# Create UV Map
if not obj.data.uv_layers:
if mode == 'OBJECT':
# Smart UV project
bpy.ops.uv.smart_project(
angle_limit=65,
island_margin=0.5,
user_area_weight=0,
use_aspect=True,
stretch_to_bounds=True
)
elif mode == 'EDIT':
# Iron Faces
bpy.ops.uv.unwrap(method='ANGLE_BASED', margin=0)
bpy.ops.uv.textools_unwrap_faces_iron()
bm = bmesh.from_edit_mesh(obj.data)
uv_layers = bm.loops.layers.uv.verify()
#Collect UV islands
bpy.ops.uv.select_all(action='SELECT')
islands = utilities_uv.getSelectionIslands(bm, uv_layers)
# Collect clusters
uvs = {}
clusters = []
uv_to_clusters = {}
vert_to_clusters = {}
face_area_view = 0
face_area_uv = 0
for face in bm.faces:
if face.select:
# Calculate triangle area for UV and View
# Triangle Verts
tri_uv = [loop[uv_layers].uv for loop in face.loops ]
tri_vt = [vert.co for vert in face.verts]
#Triangle Areas
face_area_view += math.sqrt(utilities_texel.get_area_triangle(
tri_vt[0],
tri_vt[1],
tri_vt[2]
))
face_area_uv += math.sqrt(utilities_texel.get_area_triangle(
tri_uv[0],
tri_uv[1],
tri_uv[2]
))
for i in range(len(face.loops)):
v = face.loops[i]
uv = Get_UVSet(uvs, bm, uv_layers, face.index, i)
# # clusters
isMerged = False
for cluster in clusters:
d = (uv.pos() - cluster.uvs[0].pos()).length
if d <= 0.0000001:
#Merge
cluster.append(uv)
uv_to_clusters[uv] = cluster
if v not in vert_to_clusters:
vert_to_clusters[v] = cluster
isMerged = True;
break;
if not isMerged:
#New Group
clusters.append( UVCluster(v, [uv]) )
uv_to_clusters[uv] = clusters[-1]
if v not in vert_to_clusters:
vert_to_clusters[v] = clusters[-1]
scale = face_area_view / face_area_uv
print("Scale {}x {} | {}".format(scale, face_area_view, face_area_uv))
print("Islands {}x".format(len(islands)))
print("UV Vert Clusters {}x".format(len(clusters)))
m_vert_cluster = []
m_verts_org = []
m_verts_A = []
m_verts_B = []
m_faces = []
for island in islands:
for face in island:
f = []
for i in range(len(face.loops)):
v = face.loops[i].vert
uv = Get_UVSet(uvs, bm, uv_layers, face.index, i)
c = uv_to_clusters[ uv ]
index = 0
if c in m_vert_cluster:
index = m_vert_cluster.index(c)
else:
index = len(m_vert_cluster)
m_vert_cluster.append(c)
m_verts_org.append(v)
m_verts_A.append( Vector((uv.pos().x*scale - scale/2, uv.pos().y*scale -scale/2, 0)) )
m_verts_B.append( obj.matrix_world @ v.co - bpy.context.scene.cursor.location )
f.append(index)
m_faces.append(f)
# Add UV bounds as edges
verts = [
Vector((-scale/2, -scale/2, 0)),
Vector(( scale/2, -scale/2, 0)),
Vector(( scale/2, scale/2, 0)),
Vector((-scale/2, scale/2, 0)),
]
m_verts_A = m_verts_A+verts;
m_verts_B = m_verts_B+verts;
bpy.ops.object.mode_set(mode='OBJECT')
# Create Mesh
mesh = bpy.data.meshes.new("mesh_texture")
mesh.from_pydata(m_verts_A, [], m_faces)
mesh.update()
mesh_obj = bpy.data.objects.new("UV_mesh {0}".format(obj.name), mesh)
mesh_obj.location = bpy.context.scene.cursor.location
bpy.context.collection.objects.link(mesh_obj)
# Add shape keys
mesh_obj.shape_key_add(name="uv", from_mix=True)
mesh_obj.shape_key_add(name="model", from_mix=True)
mesh_obj.active_shape_key_index = 1
# Select
bpy.context.view_layer.objects.active = mesh_obj
mesh_obj.select_set( state = True, view_layer = None)
bpy.ops.object.mode_set(mode='EDIT')
bm = bmesh.from_edit_mesh(mesh_obj.data)
if hasattr(bm.faces, "ensure_lookup_table"):
bm.faces.ensure_lookup_table()
bm.verts.ensure_lookup_table()
bm.edges.new((bm.verts[-4], bm.verts[-3]))
bm.edges.new((bm.verts[-3], bm.verts[-2]))
bm.edges.new((bm.verts[-2], bm.verts[-1]))
bm.edges.new((bm.verts[-1], bm.verts[-4]))
for i in range(len(m_verts_B)):
bm.verts[i].co = m_verts_B[i]
# Split concave faces to resolve issues with Shape deform
bpy.context.object.active_shape_key_index = 0
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE')
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.mesh.vert_connect_concave()
bpy.ops.object.mode_set(mode='OBJECT')
# Display as edges only
mesh_obj.show_wire = True
mesh_obj.show_all_edges = True
# mesh_obj.data.display_type = 'WIRE' #Esta linea deberia llevarte a la opcion wireframe
bpy.ops.object.select_all(action='DESELECT')
mesh_obj.select_set( state = True, view_layer = None)
bpy.context.view_layer.objects.active = mesh_obj
bpy.ops.object.select_all(action='DESELECT')
mesh_obj.select_set( state = True, view_layer = None)
bpy.context.view_layer.objects.active = mesh_obj
def Get_UVSet(uvs, bm, layer, index_face, index_loop):
index = get_uv_index(index_face, index_loop)
if index not in uvs:
uvs[index] = UVSet(bm, layer, index_face, index_loop)
index = get_uv_index(index_face, index_loop)
if index not in uvs:
uvs[index] = UVSet(bm, layer, index_face, index_loop)
return uvs[index]
return uvs[index]
class UVSet:
bm = None
layer = None
index_face = 0
index_loop = 0
bm = None
layer = None
index_face = 0
index_loop = 0
def __init__(self, bm, layer, index_face, index_loop):
self.bm = bm
self.layer = layer
self.index_face = index_face
self.index_loop = index_loop
def uv(self):
face = self.bm.faces[self.index_face]
return face.loops[self.index_loop][self.layer]
def __init__(self, bm, layer, index_face, index_loop):
self.bm = bm
self.layer = layer
self.index_face = index_face
self.index_loop = index_loop
def uv(self):
face = self.bm.faces[self.index_face]
return face.loops[self.index_loop][self.layer]
def pos(self):
return self.uv().uv
def pos(self):
return self.uv().uv
def vertex(self):
return face.loops[self.index_loop].vertex
def vertex(self):
return face.loops[self.index_loop].vertex
def get_uv_index(index_face, index_loop):
return (index_face*1000000)+index_loop
return (index_face*1000000)+index_loop
class UVCluster:
uvs = []
vertex = None
def __init__(self, vertex, uvs):
self.vertex = vertex
self.uvs = uvs
def append(self, uv):
self.uvs.append(uv)
uvs = []
vertex = None
def __init__(self, vertex, uvs):
self.vertex = vertex
self.uvs = uvs
def append(self, uv):
self.uvs.append(uv)
bpy.utils.register_class(op)

@ -12,182 +12,182 @@ from . import utilities_ui
class op(bpy.types.Operator):
bl_idname = "uv.textools_meshtex_pattern"
bl_label = "Create Pattern"
bl_description = "Create mesh pattern"
bl_options = {'REGISTER', 'UNDO'}
bl_idname = "uv.textools_meshtex_pattern"
bl_label = "Create Pattern"
bl_description = "Create mesh pattern"
bl_options = {'REGISTER', 'UNDO'}
mode : bpy.props.EnumProperty(items=
[('hexagon', 'Hexagons', ''),
('triangle', 'Triangles', ''),
('diamond', 'Diamonds', ''),
('rectangle', 'Rectangles', ''),
('stripe', 'Stripes', ''),
('brick', 'Bricks', '')],
name = "Mode",
default = 'brick'
)
mode : bpy.props.EnumProperty(items=
[('hexagon', 'Hexagons', ''),
('triangle', 'Triangles', ''),
('diamond', 'Diamonds', ''),
('rectangle', 'Rectangles', ''),
('stripe', 'Stripes', ''),
('brick', 'Bricks', '')],
name = "Mode",
default = 'brick'
)
size : bpy.props.IntProperty(
name = "Size",
description = "Size X and Y of the repetition",
default = 4,
min = 1,
max = 128
)
size : bpy.props.IntProperty(
name = "Size",
description = "Size X and Y of the repetition",
default = 4,
min = 1,
max = 128
)
scale : bpy.props.FloatProperty(
name = "Scale",
description = "Scale of the mesh pattern",
default = 1,
min = 0
)
scale : bpy.props.FloatProperty(
name = "Scale",
description = "Scale of the mesh pattern",
default = 1,
min = 0
)
@classmethod
def poll(cls, context):
@classmethod
def poll(cls, context):
if bpy.context.active_object and bpy.context.active_object.mode != 'OBJECT':
return False
if bpy.context.active_object and bpy.context.active_object.mode != 'OBJECT':
return False
return True
return True
def draw(self, context):
layout = self.layout
layout.prop(self, "mode")
layout.prop(self, "size")
layout.prop(self, "scale")
def draw(self, context):
layout = self.layout
layout.prop(self, "mode")
layout.prop(self, "size")
layout.prop(self, "scale")
def execute(self, context):
create_pattern(self, self.mode, self.size, self.scale)
return {'FINISHED'}
def execute(self, context):
create_pattern(self, self.mode, self.size, self.scale)
return {'FINISHED'}
def AddArray(name, offset_x, offset_y, count):
modifier = bpy.context.object.modifiers.new(name=name, type='ARRAY')
# modifier = bpy.context.object.modifiers.new(name="{}_{}".format(name,count), type='ARRAY')
modifier.relative_offset_displace[0] = offset_x
modifier.relative_offset_displace[1] = offset_y
modifier.count = count
modifier.show_expanded = False
return modifier
modifier = bpy.context.object.modifiers.new(name=name, type='ARRAY')
# modifier = bpy.context.object.modifiers.new(name="{}_{}".format(name,count), type='ARRAY')
modifier.relative_offset_displace[0] = offset_x
modifier.relative_offset_displace[1] = offset_y
modifier.count = count
modifier.show_expanded = False
return modifier
def create_pattern(self, mode, size, scale):
print("Create pattern {}".format(mode))
# bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
context_override = None
if bpy.context.area.type != 'VIEW_3D':
context_override = utilities_ui.GetContextView3D()
if not context_override:
self.report({'ERROR_INVALID_INPUT'}, "This tool requires an available View3D view.")
return
print("Mode '{}' size: '{}'".format(mode, size))
if mode == 'hexagon':
bpy.ops.mesh.primitive_circle_add(vertices=6, radius=scale, fill_type='NGON')
bpy.ops.object.mode_set(mode = 'EDIT')
if context_override:
bpy.ops.transform.rotate(context_override, value=math.pi*0.5, orient_axis='Z')
else:
bpy.ops.transform.rotate(value=math.pi*0.5, orient_axis='Z')
bpy.ops.object.mode_set(mode = 'OBJECT')
AddArray("Array0", 0.75,-0.5,2)
AddArray("Array1", 0,-0.66666666666,size)
AddArray("Array2", 1 - (0.5/3.5),0,size*0.66)
elif mode == 'triangle':
bpy.ops.mesh.primitive_circle_add(vertices=3, radius=scale, fill_type='NGON')
bpy.ops.object.mode_set(mode = 'EDIT')
if context_override:
bpy.ops.transform.translate(context_override, value=(0, scale*0.5, 0), constraint_axis=(False, True, False))
else:
bpy.ops.transform.translate(value=(0, scale*0.5, 0), constraint_axis=(False, True, False))
bpy.ops.object.mode_set(mode = 'OBJECT')
modifier = bpy.context.object.modifiers.new(name="Mirror", type='MIRROR')
modifier.use_axis[0] = False
modifier.use_axis[1] = True
modifier.show_expanded = False
AddArray("Array0", 0.5,-0.5,2)
AddArray("Array1", 1-1/3.0,0,size)
AddArray("Array1", 0,-(1-1/3.0),size*0.66)
elif mode == 'rectangle':
bpy.ops.mesh.primitive_plane_add(size=scale)
AddArray("Array0", 1,0,size)
AddArray("Array1", 0,-1,size)
elif mode == 'diamond':
bpy.ops.mesh.primitive_plane_add(size=scale)
bpy.ops.object.mode_set(mode = 'EDIT')
if context_override:
bpy.ops.transform.rotate(context_override, value=math.pi*0.25, orient_axis='Z')
else:
bpy.ops.transform.rotate(value=math.pi*0.25, orient_axis='Z')
bpy.ops.object.mode_set(mode = 'OBJECT')
AddArray("Array0", 0.5,-0.5,2)
AddArray("Array1", 1-1/3,0,size)
AddArray("Array2", 0,-(1-1/3),size)
elif mode == 'brick':
bpy.ops.mesh.primitive_plane_add(size=scale)
bpy.ops.object.mode_set(mode = 'EDIT')
if context_override:
bpy.ops.transform.resize(context_override, value=(1, 0.5, 1), constraint_axis=(True, True, False), orient_type='GLOBAL')
else:
bpy.ops.transform.resize(value=(1, 0.5, 1), constraint_axis=(True, True, False), orient_type='GLOBAL')
bpy.ops.object.mode_set(mode = 'OBJECT')
AddArray("Array0", 0.5,-1,2)
AddArray("Array1", 1-(1/3),0,size)
AddArray("Array2", 0,-1,size)
elif mode == 'stripe':
bpy.ops.mesh.primitive_plane_add(size=1)
bpy.ops.object.mode_set(mode = 'EDIT')
if context_override:
bpy.ops.transform.resize(context_override, value=(0.5, size/2, 1), constraint_axis=(True, True, False), orient_type='GLOBAL')
bpy.ops.transform.resize(context_override, value=(scale, scale, 1), constraint_axis=(True, True, False), orient_type='GLOBAL')
bpy.ops.transform.translate(context_override, value=(0, (-size/2)*scale, 0), constraint_axis=(False, True, False))
else:
bpy.ops.transform.resize(value=(0.5, size/2, 1), constraint_axis=(True, True, False), orient_type='GLOBAL')
bpy.ops.transform.resize(value=(scale, scale, 1), constraint_axis=(True, True, False), orient_type='GLOBAL')
bpy.ops.transform.translate(value=(0, (-size/2)*scale, 0), constraint_axis=(False, True, False))
bpy.ops.object.mode_set(mode = 'OBJECT')
AddArray("Array0", 1,0, size)
# if bpy.context.object:
# bpy.context.object.name = "pattern_{}".format(mode)
# bpy.context.object.show_wire = True
print("Create pattern {}".format(mode))
# bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
context_override = None
if bpy.context.area.type != 'VIEW_3D':
context_override = utilities_ui.GetContextView3D()
if not context_override:
self.report({'ERROR_INVALID_INPUT'}, "This tool requires an available View3D view.")
return
print("Mode '{}' size: '{}'".format(mode, size))
if mode == 'hexagon':
bpy.ops.mesh.primitive_circle_add(vertices=6, radius=scale, fill_type='NGON')
bpy.ops.object.mode_set(mode = 'EDIT')
if context_override:
bpy.ops.transform.rotate(context_override, value=math.pi*0.5, orient_axis='Z')
else:
bpy.ops.transform.rotate(value=math.pi*0.5, orient_axis='Z')
bpy.ops.object.mode_set(mode = 'OBJECT')
AddArray("Array0", 0.75,-0.5,2)
AddArray("Array1", 0,-0.66666666666,size)
AddArray("Array2", 1 - (0.5/3.5),0,size*0.66)
elif mode == 'triangle':
bpy.ops.mesh.primitive_circle_add(vertices=3, radius=scale, fill_type='NGON')
bpy.ops.object.mode_set(mode = 'EDIT')
if context_override:
bpy.ops.transform.translate(context_override, value=(0, scale*0.5, 0), constraint_axis=(False, True, False))
else:
bpy.ops.transform.translate(value=(0, scale*0.5, 0), constraint_axis=(False, True, False))
bpy.ops.object.mode_set(mode = 'OBJECT')
modifier = bpy.context.object.modifiers.new(name="Mirror", type='MIRROR')
modifier.use_axis[0] = False
modifier.use_axis[1] = True
modifier.show_expanded = False
AddArray("Array0", 0.5,-0.5,2)
AddArray("Array1", 1-1/3.0,0,size)
AddArray("Array1", 0,-(1-1/3.0),size*0.66)
elif mode == 'rectangle':
bpy.ops.mesh.primitive_plane_add(size=scale)
AddArray("Array0", 1,0,size)
AddArray("Array1", 0,-1,size)
elif mode == 'diamond':
bpy.ops.mesh.primitive_plane_add(size=scale)
bpy.ops.object.mode_set(mode = 'EDIT')
if context_override:
bpy.ops.transform.rotate(context_override, value=math.pi*0.25, orient_axis='Z')
else:
bpy.ops.transform.rotate(value=math.pi*0.25, orient_axis='Z')
bpy.ops.object.mode_set(mode = 'OBJECT')
AddArray("Array0", 0.5,-0.5,2)
AddArray("Array1", 1-1/3,0,size)
AddArray("Array2", 0,-(1-1/3),size)
elif mode == 'brick':
bpy.ops.mesh.primitive_plane_add(size=scale)
bpy.ops.object.mode_set(mode = 'EDIT')
if context_override:
bpy.ops.transform.resize(context_override, value=(1, 0.5, 1), constraint_axis=(True, True, False), orient_type='GLOBAL')
else:
bpy.ops.transform.resize(value=(1, 0.5, 1), constraint_axis=(True, True, False), orient_type='GLOBAL')
bpy.ops.object.mode_set(mode = 'OBJECT')
AddArray("Array0", 0.5,-1,2)
AddArray("Array1", 1-(1/3),0,size)
AddArray("Array2", 0,-1,size)
elif mode == 'stripe':
bpy.ops.mesh.primitive_plane_add(size=1)
bpy.ops.object.mode_set(mode = 'EDIT')
if context_override:
bpy.ops.transform.resize(context_override, value=(0.5, size/2, 1), constraint_axis=(True, True, False), orient_type='GLOBAL')
bpy.ops.transform.resize(context_override, value=(scale, scale, 1), constraint_axis=(True, True, False), orient_type='GLOBAL')
bpy.ops.transform.translate(context_override, value=(0, (-size/2)*scale, 0), constraint_axis=(False, True, False))
else:
bpy.ops.transform.resize(value=(0.5, size/2, 1), constraint_axis=(True, True, False), orient_type='GLOBAL')
bpy.ops.transform.resize(value=(scale, scale, 1), constraint_axis=(True, True, False), orient_type='GLOBAL')
bpy.ops.transform.translate(value=(0, (-size/2)*scale, 0), constraint_axis=(False, True, False))
bpy.ops.object.mode_set(mode = 'OBJECT')
AddArray("Array0", 1,0, size)
# if bpy.context.object:
# bpy.context.object.name = "pattern_{}".format(mode)
# bpy.context.object.show_wire = True
bpy.utils.register_class(op)

@ -12,64 +12,64 @@ from . import utilities_meshtex
class op(bpy.types.Operator):
bl_idname = "uv.textools_meshtex_trim"
bl_label = "Trim"
bl_description = "Trim Mesh Texture"
bl_options = {'REGISTER', 'UNDO'}
bl_idname = "uv.textools_meshtex_trim"
bl_label = "Trim"
bl_description = "Trim Mesh Texture"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
if not bpy.context.active_object or bpy.context.active_object.mode != 'OBJECT':
return False
if len(bpy.context.selected_objects) >= 1:
# Find a UV mesh
if utilities_meshtex.find_uv_mesh(bpy.context.selected_objects):
# Find 1 or more meshes to wrap
if len( utilities_meshtex.find_texture_meshes(bpy.context.selected_objects)) > 0:
return True
@classmethod
def poll(cls, context):
if not bpy.context.active_object or bpy.context.active_object.mode != 'OBJECT':
return False
if len(bpy.context.selected_objects) >= 1:
# Find a UV mesh
if utilities_meshtex.find_uv_mesh(bpy.context.selected_objects):
# Find 1 or more meshes to wrap
if len( utilities_meshtex.find_texture_meshes(bpy.context.selected_objects)) > 0:
return True
return False
return False
def execute(self, context):
trim(self)
return {'FINISHED'}
def execute(self, context):
trim(self)
return {'FINISHED'}
def trim(self):
# Wrap the mesh texture around the
print("Trim Mesh Texture :)")
# Wrap the mesh texture around the
print("Trim Mesh Texture :)")
# Collect UV mesh
obj_uv = utilities_meshtex.find_uv_mesh(bpy.context.selected_objects)
if not obj_uv:
self.report({'ERROR_INVALID_INPUT'}, "No UV mesh found" )
return
# Collect UV mesh
obj_uv = utilities_meshtex.find_uv_mesh(bpy.context.selected_objects)
if not obj_uv:
self.report({'ERROR_INVALID_INPUT'}, "No UV mesh found" )
return
# Collect texture meshes
obj_textures = utilities_meshtex.find_texture_meshes( bpy.context.selected_objects )
# Collect texture meshes
obj_textures = utilities_meshtex.find_texture_meshes( bpy.context.selected_objects )
if len(obj_textures) == 0:
self.report({'ERROR_INVALID_INPUT'}, "No meshes found for mesh textures" )
return
if len(obj_textures) == 0:
self.report({'ERROR_INVALID_INPUT'}, "No meshes found for mesh textures" )
return
# Setup Thickness
utilities_meshtex.uv_mesh_fit(obj_uv, obj_textures)
# Setup Thickness
utilities_meshtex.uv_mesh_fit(obj_uv, obj_textures)
# Apply bool modifier to trim
for obj in obj_textures:
name = "Trim UV"
# Apply bool modifier to trim
for obj in obj_textures:
name = "Trim UV"
if name in obj.modifiers:
obj.modifiers.remove( obj.modifiers[name] )
if name in obj.modifiers:
obj.modifiers.remove( obj.modifiers[name] )
modifier_bool = obj.modifiers.new(name=name, type='BOOLEAN')
modifier_bool.object = obj_uv
modifier_bool = obj.modifiers.new(name=name, type='BOOLEAN')
modifier_bool.object = obj_uv
bpy.ops.ui.textools_popup('INVOKE_DEFAULT', message="Collapse modifiers before wrapping")
bpy.ops.ui.textools_popup('INVOKE_DEFAULT', message="Collapse modifiers before wrapping")
bpy.utils.register_class(op)

@ -10,61 +10,61 @@ from . import utilities_meshtex
def is_available():
# If the selection contains a boolean modifier
obj_textures = utilities_meshtex.find_texture_meshes(bpy.context.selected_objects)
for obj in obj_textures:
for modifier in obj.modifiers:
if modifier.type == 'BOOLEAN':
return True
return False
# If the selection contains a boolean modifier
obj_textures = utilities_meshtex.find_texture_meshes(bpy.context.selected_objects)
for obj in obj_textures:
for modifier in obj.modifiers:
if modifier.type == 'BOOLEAN':
return True
return False
class op(bpy.types.Operator):
bl_idname = "uv.textools_meshtex_trimcollapse"
bl_label = "Collapse"
bl_description = "Trim Mesh Texture"
bl_options = {'REGISTER', 'UNDO'}
bl_idname = "uv.textools_meshtex_trimcollapse"
bl_label = "Collapse"
bl_description = "Trim Mesh Texture"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
if not bpy.context.active_object or bpy.context.active_object.mode != 'OBJECT':
return False
return is_available()
@classmethod
def poll(cls, context):
if not bpy.context.active_object or bpy.context.active_object.mode != 'OBJECT':
return False
return is_available()
def execute(self, context):
collapse(self)
return {'FINISHED'}
def execute(self, context):
collapse(self)
return {'FINISHED'}
def collapse(self):
# Collect texture meshes
obj_textures = utilities_meshtex.find_texture_meshes( bpy.context.selected_objects )
# Collect texture meshes
obj_textures = utilities_meshtex.find_texture_meshes( bpy.context.selected_objects )
previous_selection = bpy.context.selected_objects.copy()
previous_active = bpy.context.view_layer.objects.active
previous_selection = bpy.context.selected_objects.copy()
previous_active = bpy.context.view_layer.objects.active
if len(obj_textures) == 0:
self.report({'ERROR_INVALID_INPUT'}, "No meshes found for mesh textures" )
return
if len(obj_textures) == 0:
self.report({'ERROR_INVALID_INPUT'}, "No meshes found for mesh textures" )
return
# Apply bool modifier to trim
for obj in obj_textures:
bpy.ops.object.select_all(action='DESELECT')
obj.select_set( state = True, view_layer = None)
bpy.context.view_layer.objects.active = obj
bpy.ops.object.convert(target='MESH')
# Apply bool modifier to trim
for obj in obj_textures:
bpy.ops.object.select_all(action='DESELECT')
obj.select_set( state = True, view_layer = None)
bpy.context.view_layer.objects.active = obj
bpy.ops.object.convert(target='MESH')
# restore selection
bpy.ops.object.select_all(action='DESELECT')
for obj in previous_selection:
obj.select_set( state = True, view_layer = None)
bpy.context.view_layer.objects.active = previous_active
# restore selection
bpy.ops.object.select_all(action='DESELECT')
for obj in previous_selection:
obj.select_set( state = True, view_layer = None)
bpy.context.view_layer.objects.active = previous_active
bpy.ops.ui.textools_popup('INVOKE_DEFAULT', message="{}x objects have been collapsed".format(len(obj_textures)))
bpy.ops.ui.textools_popup('INVOKE_DEFAULT', message="{}x objects have been collapsed".format(len(obj_textures)))
bpy.utils.register_class(op)

@ -10,77 +10,77 @@ from . import utilities_meshtex
class op(bpy.types.Operator):
bl_idname = "uv.textools_meshtex_wrap"
bl_label = "Wrap Mesh Texture"
bl_description = "Swap UV to XYZ coordinates"
bl_options = {'REGISTER', 'UNDO'}
bl_idname = "uv.textools_meshtex_wrap"
bl_label = "Wrap Mesh Texture"
bl_description = "Swap UV to XYZ coordinates"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
if not bpy.context.active_object or bpy.context.active_object.mode != 'OBJECT':
return False
# Wrap texture mesh around UV mesh
if len(bpy.context.selected_objects) >= 1:
# Find a UV mesh
if utilities_meshtex.find_uv_mesh(bpy.context.selected_objects):
# Find 1 or more meshes to wrap
if len( utilities_meshtex.find_texture_meshes(bpy.context.selected_objects)) > 0:
return True
@classmethod
def poll(cls, context):
if not bpy.context.active_object or bpy.context.active_object.mode != 'OBJECT':
return False
# Wrap texture mesh around UV mesh
if len(bpy.context.selected_objects) >= 1:
# Find a UV mesh
if utilities_meshtex.find_uv_mesh(bpy.context.selected_objects):
# Find 1 or more meshes to wrap
if len( utilities_meshtex.find_texture_meshes(bpy.context.selected_objects)) > 0:
return True
return False
return False
def execute(self, context):
wrap_meshtex(self)
return {'FINISHED'}
def execute(self, context):
wrap_meshtex(self)
return {'FINISHED'}
def wrap_meshtex(self):
# Wrap the mesh texture around the
print("Wrap Mesh Texture :)")
# Collect UV mesh
obj_uv = utilities_meshtex.find_uv_mesh(bpy.context.selected_objects)
if not obj_uv:
self.report({'ERROR_INVALID_INPUT'}, "No UV mesh found" )
return
# Collect texture meshes
obj_textures = utilities_meshtex.find_texture_meshes( bpy.context.selected_objects )
if len(obj_textures) == 0:
self.report({'ERROR_INVALID_INPUT'}, "No meshes found for mesh textures" )
return
print("Wrap {} texture meshes".format(len(obj_textures)))
# Undo wrapping
if bpy.context.scene.texToolsSettings.meshtexture_wrap > 0:
bpy.context.scene.texToolsSettings.meshtexture_wrap = 0
# Clear modifiers
utilities_meshtex.uv_mesh_clear(obj_uv)
return
# Setup Thickness
utilities_meshtex.uv_mesh_fit(obj_uv, obj_textures)
for obj in obj_textures:
# Delete previous modifiers
for modifier in obj.modifiers:
if modifier.type == 'SURFACE_DEFORM':
obj.modifiers.remove(modifier)
break
# Add mesh modifier
modifier_deform = obj.modifiers.new(name="SurfaceDeform", type='SURFACE_DEFORM')
modifier_deform.target = obj_uv
obj.select_set( state = True, view_layer = None)
bpy.context.view_layer.objects.active = obj
bpy.ops.object.surfacedeform_bind(modifier="SurfaceDeform")
# Apply wrapped morph state
bpy.context.scene.texToolsSettings.meshtexture_wrap = 1
# Wrap the mesh texture around the
print("Wrap Mesh Texture :)")
# Collect UV mesh
obj_uv = utilities_meshtex.find_uv_mesh(bpy.context.selected_objects)
if not obj_uv:
self.report({'ERROR_INVALID_INPUT'}, "No UV mesh found" )
return
# Collect texture meshes
obj_textures = utilities_meshtex.find_texture_meshes( bpy.context.selected_objects )
if len(obj_textures) == 0:
self.report({'ERROR_INVALID_INPUT'}, "No meshes found for mesh textures" )
return
print("Wrap {} texture meshes".format(len(obj_textures)))
# Undo wrapping
if bpy.context.scene.texToolsSettings.meshtexture_wrap > 0:
bpy.context.scene.texToolsSettings.meshtexture_wrap = 0
# Clear modifiers
utilities_meshtex.uv_mesh_clear(obj_uv)
return
# Setup Thickness
utilities_meshtex.uv_mesh_fit(obj_uv, obj_textures)
for obj in obj_textures:
# Delete previous modifiers
for modifier in obj.modifiers:
if modifier.type == 'SURFACE_DEFORM':
obj.modifiers.remove(modifier)
break
# Add mesh modifier
modifier_deform = obj.modifiers.new(name="SurfaceDeform", type='SURFACE_DEFORM')
modifier_deform.target = obj_uv
obj.select_set( state = True, view_layer = None)
bpy.context.view_layer.objects.active = obj
bpy.ops.object.surfacedeform_bind(modifier="SurfaceDeform")
# Apply wrapped morph state
bpy.context.scene.texToolsSettings.meshtexture_wrap = 1
bpy.utils.register_class(op)

File diff suppressed because it is too large Load Diff

@ -10,124 +10,124 @@ import imp
imp.reload(utilities_uv)
class op(bpy.types.Operator):
bl_idname = "uv.textools_select_islands_flipped"
bl_label = "Select Flipped"
bl_description = "Select all flipped UV islands"
bl_options = {'REGISTER', 'UNDO'}
bl_idname = "uv.textools_select_islands_flipped"
bl_label = "Select Flipped"
bl_description = "Select all flipped UV islands"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
if not bpy.context.active_object:
return False
@classmethod
def poll(cls, context):
if not bpy.context.active_object:
return False
if bpy.context.active_object.type != 'MESH':
return False
if bpy.context.active_object.type != 'MESH':
return False
#Only in Edit mode
if bpy.context.active_object.mode != 'EDIT':
return False
#Only in Edit mode
if bpy.context.active_object.mode != 'EDIT':
return False
#Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR':
return False
#Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR':
return False
##Requires UV map
if not bpy.context.object.data.uv_layers:
return False
##Requires UV map
if not bpy.context.object.data.uv_layers:
return False
#Not in Synced mode
if bpy.context.scene.tool_settings.use_uv_select_sync:
return False
return True
#Not in Synced mode
if bpy.context.scene.tool_settings.use_uv_select_sync:
return False
return True
def execute(self, context):
select_flipped(context)
return {'FINISHED'}
def execute(self, context):
select_flipped(context)
return {'FINISHED'}
def select_flipped(context):
bm = bmesh.from_edit_mesh(bpy.context.active_object.data)
uv_layers = bm.loops.layers.uv.verify()
bm = bmesh.from_edit_mesh(bpy.context.active_object.data)
uv_layers = bm.loops.layers.uv.verify()
bpy.context.scene.tool_settings.uv_select_mode = 'FACE'
bpy.ops.uv.select_all(action='SELECT')
bpy.context.scene.tool_settings.uv_select_mode = 'FACE'
bpy.ops.uv.select_all(action='SELECT')
islands = utilities_uv.getSelectionIslands()
islands = utilities_uv.getSelectionIslands()
bpy.context.scene.tool_settings.uv_select_mode = 'FACE'
bpy.context.scene.tool_settings.use_uv_select_sync = False
bpy.ops.uv.select_all(action='DESELECT')
bpy.context.scene.tool_settings.uv_select_mode = 'FACE'
bpy.context.scene.tool_settings.use_uv_select_sync = False
bpy.ops.uv.select_all(action='DESELECT')
for island in islands:
for island in islands:
is_flipped = False
for face in island:
if is_flipped:
break
is_flipped = False
for face in island:
if is_flipped:
break
# Using 'Sum of Edges' to detect counter clockwise https://stackoverflow.com/questions/1165647/how-to-determine-if-a-list-of-polygon-points-are-in-clockwise-order
sum = 0
count = len(face.loops)
for i in range(count):
uv_A = face.loops[i][uv_layers].uv
uv_B = face.loops[(i+1)%count][uv_layers].uv
sum += (uv_B.x - uv_A.x) * (uv_B.y + uv_A.y)
# Using 'Sum of Edges' to detect counter clockwise https://stackoverflow.com/questions/1165647/how-to-determine-if-a-list-of-polygon-points-are-in-clockwise-order
sum = 0
count = len(face.loops)
for i in range(count):
uv_A = face.loops[i][uv_layers].uv
uv_B = face.loops[(i+1)%count][uv_layers].uv
sum += (uv_B.x - uv_A.x) * (uv_B.y + uv_A.y)
if sum > 0:
# Flipped
is_flipped = True
break
if sum > 0:
# Flipped
is_flipped = True
break
# Select Island if flipped
if is_flipped:
for face in island:
for loop in face.loops:
loop[uv_layers].select = True
# Select Island if flipped
if is_flipped:
for face in island:
for loop in face.loops:
loop[uv_layers].select = True
class Island_bounds:
faces = []
center = Vector([0,0])
min = Vector([0,0])
max = Vector([0,0])
def __init__(self, faces):
bm = bmesh.from_edit_mesh(bpy.context.active_object.data);
uv_layers = bm.loops.layers.uv.verify();
# Collect topology stats
self.faces = faces
#Select Island
bpy.ops.uv.select_all(action='DESELECT')
utilities_uv.set_selected_faces(faces)
bounds = utilities_uv.getSelectionBBox()
self.center = bounds['center']
self.min = bounds['min']
self.max = bounds['max']
def isEqual(A, B):
# Bounding Box AABB intersection?
min_x = max(A.min.x, B.min.x)
min_y = max(A.min.y, B.min.y)
max_x = min(A.max.x, B.max.x)
max_y = min(A.max.y, B.max.y)
if not (max_x < min_x or max_y < min_y):
return True
return False
faces = []
center = Vector([0,0])
min = Vector([0,0])
max = Vector([0,0])
def __init__(self, faces):
bm = bmesh.from_edit_mesh(bpy.context.active_object.data);
uv_layers = bm.loops.layers.uv.verify();
# Collect topology stats
self.faces = faces
#Select Island
bpy.ops.uv.select_all(action='DESELECT')
utilities_uv.set_selected_faces(faces)
bounds = utilities_uv.getSelectionBBox()
self.center = bounds['center']
self.min = bounds['min']
self.max = bounds['max']
def isEqual(A, B):
# Bounding Box AABB intersection?
min_x = max(A.min.x, B.min.x)
min_y = max(A.min.y, B.min.y)
max_x = min(A.max.x, B.max.x)
max_y = min(A.max.y, B.max.y)
if not (max_x < min_x or max_y < min_y):
return True
return False
bpy.utils.register_class(op)

@ -9,122 +9,122 @@ from . import utilities_uv
class op(bpy.types.Operator):
bl_idname = "uv.textools_select_islands_identical"
bl_label = "Select identical"
bl_description = "Select identical UV islands with similar topology"
bl_options = {'REGISTER', 'UNDO'}
bl_idname = "uv.textools_select_islands_identical"
bl_label = "Select identical"
bl_description = "Select identical UV islands with similar topology"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
if not bpy.context.active_object:
return False
if bpy.context.active_object.type != 'MESH':
return False
@classmethod
def poll(cls, context):
if not bpy.context.active_object:
return False
if bpy.context.active_object.type != 'MESH':
return False
#Only in Edit mode
if bpy.context.active_object.mode != 'EDIT':
return False
#Only in Edit mode
if bpy.context.active_object.mode != 'EDIT':
return False
#Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR':
return False
#Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR':
return False
##Requires UV map
if not bpy.context.object.data.uv_layers:
return False
##Requires UV map
if not bpy.context.object.data.uv_layers:
return False
#Not in Synced mode
if bpy.context.scene.tool_settings.use_uv_select_sync:
return False
#Not in Synced mode
if bpy.context.scene.tool_settings.use_uv_select_sync:
return False
return True
return True
def execute(self, context):
swap(self, context)
return {'FINISHED'}
def execute(self, context):
swap(self, context)
return {'FINISHED'}
def swap(self, context):
bm = bmesh.from_edit_mesh(bpy.context.active_object.data)
uv_layers = bm.loops.layers.uv.verify()
bm = bmesh.from_edit_mesh(bpy.context.active_object.data)
uv_layers = bm.loops.layers.uv.verify()
# Get selected island
islands = utilities_uv.getSelectionIslands()
# Get selected island
islands = utilities_uv.getSelectionIslands()
if len(islands) != 1:
self.report({'ERROR_INVALID_INPUT'}, "Please select only 1 UV Island")
return
island_stats_source = Island_stats(islands[0])
if len(islands) != 1:
self.report({'ERROR_INVALID_INPUT'}, "Please select only 1 UV Island")
return
island_stats_source = Island_stats(islands[0])
bpy.context.scene.tool_settings.uv_select_mode = 'FACE'
bpy.ops.uv.select_all(action='SELECT')
bpy.context.scene.tool_settings.uv_select_mode = 'FACE'
bpy.ops.uv.select_all(action='SELECT')
islands_all = utilities_uv.getSelectionIslands()
islands_equal = []
for island in islands_all:
island_stats = Island_stats(island)
islands_all = utilities_uv.getSelectionIslands()
islands_equal = []
for island in islands_all:
island_stats = Island_stats(island)
if island_stats_source.isEqual(island_stats):
islands_equal.append(island_stats.faces)
if island_stats_source.isEqual(island_stats):
islands_equal.append(island_stats.faces)
print("Islands: "+str(len(islands_equal))+"x")
print("Islands: "+str(len(islands_equal))+"x")
bpy.ops.uv.select_all(action='DESELECT')
for island in islands_equal:
for face in island:
for loop in face.loops:
if not loop[uv_layers].select:
loop[uv_layers].select = True
bpy.ops.uv.select_all(action='DESELECT')
for island in islands_equal:
for face in island:
for loop in face.loops:
if not loop[uv_layers].select:
loop[uv_layers].select = True
class Island_stats:
countFaces = 0
countVerts = 0
faces = []
area = 0
countLinkedEdges = 0
countLinkedFaces = 0
def __init__(self, faces):
bm = bmesh.from_edit_mesh(bpy.context.active_object.data);
uv_layers = bm.loops.layers.uv.verify();
# Collect topology stats
self.faces = faces
verts = []
for face in faces:
self.countFaces+=1
self.area+=face.calc_area()
for loop in face.loops:
if loop.vert not in verts:
verts.append(loop.vert)
self.countVerts+=1
self.countLinkedEdges+= len(loop.vert.link_edges)
self.countLinkedFaces+= len(loop.vert.link_faces)
def isEqual(self, other):
if self.countVerts != other.countVerts:
return False
if self.countFaces != other.countFaces:
return False
if self.countLinkedEdges != other.countLinkedEdges:
return False
if self.countLinkedFaces != other.countLinkedFaces:
return False
# area needs to be 90%+ identical
if min(self.area, other.area)/max(self.area, other.area) < 0.7:
return False
return True
countFaces = 0
countVerts = 0
faces = []
area = 0
countLinkedEdges = 0
countLinkedFaces = 0
def __init__(self, faces):
bm = bmesh.from_edit_mesh(bpy.context.active_object.data);
uv_layers = bm.loops.layers.uv.verify();
# Collect topology stats
self.faces = faces
verts = []
for face in faces:
self.countFaces+=1
self.area+=face.calc_area()
for loop in face.loops:
if loop.vert not in verts:
verts.append(loop.vert)
self.countVerts+=1
self.countLinkedEdges+= len(loop.vert.link_edges)
self.countLinkedFaces+= len(loop.vert.link_faces)
def isEqual(self, other):
if self.countVerts != other.countVerts:
return False
if self.countFaces != other.countFaces:
return False
if self.countLinkedEdges != other.countLinkedEdges:
return False
if self.countLinkedFaces != other.countLinkedFaces:
return False
# area needs to be 90%+ identical
if min(self.area, other.area)/max(self.area, other.area) < 0.7:
return False
return True
bpy.utils.register_class(op)

@ -9,70 +9,70 @@ from . import utilities_uv
from . import utilities_ui
class op(bpy.types.Operator):
bl_idname = "uv.textools_select_islands_outline"
bl_label = "Select Overlap"
bl_description = "Select island edge bounds"
bl_options = {'REGISTER', 'UNDO'}
bl_idname = "uv.textools_select_islands_outline"
bl_label = "Select Overlap"
bl_description = "Select island edge bounds"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
if not bpy.context.active_object:
return False
if bpy.context.active_object.type != 'MESH':
return False
@classmethod
def poll(cls, context):
if not bpy.context.active_object:
return False
if bpy.context.active_object.type != 'MESH':
return False
#Requires UV map
if not bpy.context.object.data.uv_layers:
return False
#Requires UV map
if not bpy.context.object.data.uv_layers:
return False
return True
return True
def execute(self, context):
select_outline(context)
return {'FINISHED'}
def execute(self, context):
select_outline(context)
return {'FINISHED'}
def select_outline(context):
#Only in Edit mode
if bpy.context.active_object.mode != 'EDIT':
bpy.ops.object.mode_set(mode='EDIT')
#Only in Edit mode
if bpy.context.active_object.mode != 'EDIT':
bpy.ops.object.mode_set(mode='EDIT')
bpy.context.scene.tool_settings.use_uv_select_sync = False
bpy.context.scene.tool_settings.use_uv_select_sync = False
bm = bmesh.from_edit_mesh(bpy.context.active_object.data);
uv_layers = bm.loops.layers.uv.verify();
bm = bmesh.from_edit_mesh(bpy.context.active_object.data);
uv_layers = bm.loops.layers.uv.verify();
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE')
bpy.ops.mesh.select_all(action='DESELECT')
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE')
bpy.ops.mesh.select_all(action='DESELECT')
# Store previous edge seams
edges_seam = [edge for edge in bm.edges if edge.seam]
# Store previous edge seams
edges_seam = [edge for edge in bm.edges if edge.seam]
contextViewUV = utilities_ui.GetContextViewUV()
if not contextViewUV:
self.report({'ERROR_INVALID_INPUT'}, "This tool requires an available UV/Image view.")
return
contextViewUV = utilities_ui.GetContextViewUV()
if not contextViewUV:
self.report({'ERROR_INVALID_INPUT'}, "This tool requires an available UV/Image view.")
return
# Create seams from islands
bpy.ops.uv.seams_from_islands(contextViewUV)
edges_islands = [edge for edge in bm.edges if edge.seam]
# Create seams from islands
bpy.ops.uv.seams_from_islands(contextViewUV)
edges_islands = [edge for edge in bm.edges if edge.seam]
# Clear seams
for edge in edges_islands:
edge.seam = False
# Clear seams
for edge in edges_islands:
edge.seam = False
# Select island edges
bpy.ops.mesh.select_all(action='DESELECT')
for edge in edges_islands:
edge.select = True
# Select island edges
bpy.ops.mesh.select_all(action='DESELECT')
for edge in edges_islands:
edge.select = True
# Restore seam selection
for edge in edges_seam:
edge.seam = True
# Restore seam selection
for edge in edges_seam:
edge.seam = True
bpy.utils.register_class(op)

@ -10,131 +10,131 @@ import imp
imp.reload(utilities_uv)
class op(bpy.types.Operator):
bl_idname = "uv.textools_select_islands_overlap"
bl_label = "Select outline"
bl_description = "Select all overlapping UV islands"
bl_options = {'REGISTER', 'UNDO'}
bl_idname = "uv.textools_select_islands_overlap"
bl_label = "Select outline"
bl_description = "Select all overlapping UV islands"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
if not bpy.context.active_object:
return False
@classmethod
def poll(cls, context):
if not bpy.context.active_object:
return False
if bpy.context.active_object.type != 'MESH':
return False
if bpy.context.active_object.type != 'MESH':
return False
#Only in Edit mode
if bpy.context.active_object.mode != 'EDIT':
return False
#Only in Edit mode
if bpy.context.active_object.mode != 'EDIT':
return False
#Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR':
return False
#Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR':
return False
##Requires UV map
if not bpy.context.object.data.uv_layers:
return False
##Requires UV map
if not bpy.context.object.data.uv_layers:
return False
#Not in Synced mode
if bpy.context.scene.tool_settings.use_uv_select_sync:
return False
return True
#Not in Synced mode
if bpy.context.scene.tool_settings.use_uv_select_sync:
return False
return True
def execute(self, context):
selectOverlap(context)
return {'FINISHED'}
def execute(self, context):
selectOverlap(context)
return {'FINISHED'}
def selectOverlap(context):
print("Execute op_select_islands_overlap")
print("Execute op_select_islands_overlap")
# https://developer.blender.org/D2865
# https://developer.blender.org/D2865
bm = bmesh.from_edit_mesh(bpy.context.active_object.data)
uv_layers = bm.loops.layers.uv.verify()
bm = bmesh.from_edit_mesh(bpy.context.active_object.data)
uv_layers = bm.loops.layers.uv.verify()
bpy.context.scene.tool_settings.uv_select_mode = 'FACE'
bpy.ops.uv.select_all(action='SELECT')
bpy.context.scene.tool_settings.uv_select_mode = 'FACE'
bpy.ops.uv.select_all(action='SELECT')
islands_all = utilities_uv.getSelectionIslands()
# count = len(islands_all)
islands_all = utilities_uv.getSelectionIslands()
# count = len(islands_all)
islands_bounds = []
for island in islands_all:
islands_bounds.append( Island_bounds( island ) )
islands_bounds = []
for island in islands_all:
islands_bounds.append( Island_bounds( island ) )
groups = []
unmatched = islands_bounds.copy()
groups = []
unmatched = islands_bounds.copy()
for islandA in islands_bounds:
if islandA in unmatched:
for islandA in islands_bounds:
if islandA in unmatched:
group = [islandA]
for islandB in unmatched:
if islandA != islandB and islandA.isEqual(islandB):
group.append(islandB)
group = [islandA]
for islandB in unmatched:
if islandA != islandB and islandA.isEqual(islandB):
group.append(islandB)
for item in group:
unmatched.remove(item)
for item in group:
unmatched.remove(item)
groups.append(group)
groups.append(group)
print("Group: {} islands, unmatched: {}x".format(len(group), len(unmatched)))
# groups.append( )
print("Group: {} islands, unmatched: {}x".format(len(group), len(unmatched)))
# groups.append( )
bpy.ops.uv.select_all(action='DESELECT')
for group in groups:
if len(group) > 1:
for i in range(1, len(group)):
utilities_uv.set_selected_faces( group[i].faces )
bpy.ops.uv.select_all(action='DESELECT')
for group in groups:
if len(group) > 1:
for i in range(1, len(group)):
utilities_uv.set_selected_faces( group[i].faces )
print("Groups: "+str(len(groups)))
print("Groups: "+str(len(groups)))
class Island_bounds:
faces = []
center = Vector([0,0])
min = Vector([0,0])
max = Vector([0,0])
def __init__(self, faces):
bm = bmesh.from_edit_mesh(bpy.context.active_object.data);
uv_layers = bm.loops.layers.uv.verify();
# Collect topology stats
self.faces = faces
#Select Island
bpy.ops.uv.select_all(action='DESELECT')
utilities_uv.set_selected_faces(faces)
bounds = utilities_uv.getSelectionBBox()
self.center = bounds['center']
self.min = bounds['min']
self.max = bounds['max']
def isEqual(A, B):
# Bounding Box AABB intersection?
min_x = max(A.min.x, B.min.x)
min_y = max(A.min.y, B.min.y)
max_x = min(A.max.x, B.max.x)
max_y = min(A.max.y, B.max.y)
if not (max_x < min_x or max_y < min_y):
return True
return False
faces = []
center = Vector([0,0])
min = Vector([0,0])
max = Vector([0,0])
def __init__(self, faces):
bm = bmesh.from_edit_mesh(bpy.context.active_object.data);
uv_layers = bm.loops.layers.uv.verify();
# Collect topology stats
self.faces = faces
#Select Island
bpy.ops.uv.select_all(action='DESELECT')
utilities_uv.set_selected_faces(faces)
bounds = utilities_uv.getSelectionBBox()
self.center = bounds['center']
self.min = bounds['min']
self.max = bounds['max']
def isEqual(A, B):
# Bounding Box AABB intersection?
min_x = max(A.min.x, B.min.x)
min_y = max(A.min.y, B.min.y)
max_x = min(A.max.x, B.max.x)
max_y = min(A.max.y, B.max.y)
if not (max_x < min_x or max_y < min_y):
return True
return False
bpy.utils.register_class(op)

@ -10,58 +10,58 @@ from . import utilities_uv
from . import utilities_ui
class op(bpy.types.Operator):
bl_idname = "uv.textools_smoothing_uv_islands"
bl_label = "Apply smooth normals and hard edges for UV Island borders."
bl_description = "Set mesh smoothing by uv islands"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
if not bpy.context.active_object:
return False
if bpy.context.active_object.type != 'MESH':
return False
bl_idname = "uv.textools_smoothing_uv_islands"
bl_label = "Apply smooth normals and hard edges for UV Island borders."
bl_description = "Set mesh smoothing by uv islands"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
if not bpy.context.active_object:
return False
if bpy.context.active_object.type != 'MESH':
return False
#Requires UV map
if not bpy.context.object.data.uv_layers:
return False
#Requires UV map
if not bpy.context.object.data.uv_layers:
return False
return True
def execute(self, context):
smooth_uv_islands(self, context)
return {'FINISHED'}
return True
def execute(self, context):
smooth_uv_islands(self, context)
return {'FINISHED'}
def smooth_uv_islands(self, context):
if bpy.context.active_object.mode != 'EDIT':
bpy.ops.object.mode_set(mode='EDIT')
if bpy.context.active_object.mode != 'EDIT':
bpy.ops.object.mode_set(mode='EDIT')
bm = bmesh.from_edit_mesh(bpy.context.active_object.data);
uv_layers = bm.loops.layers.uv.verify();
bm = bmesh.from_edit_mesh(bpy.context.active_object.data);
uv_layers = bm.loops.layers.uv.verify();
# Smooth everything
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE')
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.mesh.faces_shade_smooth()
bpy.ops.mesh.mark_sharp(clear=True)
# Smooth everything
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE')
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.mesh.faces_shade_smooth()
bpy.ops.mesh.mark_sharp(clear=True)
# Select Edges
bpy.ops.mesh.select_all(action='DESELECT')
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE')
bpy.ops.uv.textools_select_islands_outline()
bpy.ops.mesh.mark_sharp()
bpy.ops.mesh.select_all(action='DESELECT')
# Apply Edge split modifier
bpy.context.object.data.use_auto_smooth = True
bpy.context.object.data.auto_smooth_angle = math.pi
# Select Edges
bpy.ops.mesh.select_all(action='DESELECT')
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE')
bpy.ops.uv.textools_select_islands_outline()
bpy.ops.mesh.mark_sharp()
bpy.ops.mesh.select_all(action='DESELECT')
# Apply Edge split modifier
bpy.context.object.data.use_auto_smooth = True
bpy.context.object.data.auto_smooth_angle = math.pi
# bpy.ops.object.modifier_add(type='EDGE_SPLIT')
# bpy.context.object.modifiers["EdgeSplit"].use_edge_angle = False
# bpy.ops.object.modifier_add(type='EDGE_SPLIT')
# bpy.context.object.modifiers["EdgeSplit"].use_edge_angle = False
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.mode_set(mode='OBJECT')
bpy.utils.register_class(op)

@ -14,163 +14,163 @@ texture_modes = ['UV_GRID','COLOR_GRID','GRAVITY','NONE']
class op(bpy.types.Operator):
bl_idname = "uv.textools_texel_checker_map"
bl_label = "Checker Map"
bl_description = "Add a checker map to the selected model and UV view"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
if len(get_valid_objects()) == 0:
return False
bl_idname = "uv.textools_texel_checker_map"
bl_label = "Checker Map"
bl_description = "Add a checker map to the selected model and UV view"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
if len(get_valid_objects()) == 0:
return False
return True
return True
def execute(self, context):
assign_checker_map(
bpy.context.scene.texToolsSettings.size[0],
bpy.context.scene.texToolsSettings.size[1]
)
return {'FINISHED'}
def execute(self, context):
assign_checker_map(
bpy.context.scene.texToolsSettings.size[0],
bpy.context.scene.texToolsSettings.size[1]
)
return {'FINISHED'}
def assign_checker_map(size_x, size_y):
# Force Object mode
if bpy.context.view_layer.objects.active != None and bpy.context.object.mode != 'OBJECT':
bpy.ops.object.mode_set(mode='OBJECT')
# Collect Objects
objects = get_valid_objects()
if len(objects) == 0:
self.report({'ERROR_INVALID_INPUT'}, "No UV mapped objects selected" )
#Change View mode to TEXTURED
for area in bpy.context.screen.areas:
if area.type == 'VIEW_3D':
for space in area.spaces:
if space.type == 'VIEW_3D':
space.shading.type = 'MATERIAL'
if len(objects) > 0:
# Detect current Checker modes
mode_count = {}
for mode in texture_modes:
mode_count[mode] = 0
# Image sizes
image_sizes_x = []
image_sizes_y = []
# Collect current modes in selected objects
for obj in objects:
image = utilities_texel.get_object_texture_image(obj)
mode = 'NONE'
if image:
if "GRAVITY" in image.name.upper():
mode = 'GRAVITY'
elif image.generated_type in texture_modes:
# Generated checker maps
mode = image.generated_type
# Track image sizes
if image.size[0] not in image_sizes_x:
image_sizes_x.append(image.size[0])
if image.size[1] not in image_sizes_y:
image_sizes_y.append(image.size[1])
mode_count[mode]+=1
# Sort by count (returns tuple list of key,value)
mode_max_count = sorted(mode_count.items(), key=operator.itemgetter(1))
mode_max_count.reverse()
for key,val in mode_max_count:
print("{} = {}".format(key, val))
# Determine next mode
mode = 'NONE'
if mode_max_count[0][1] == 0:
# There are no maps
mode = texture_modes[0]
elif mode_max_count[0][0] in texture_modes:
if mode_max_count[-1][1] > 0:
# There is more than 0 of another mode, complete existing mode first
mode = mode_max_count[0][0]
else:
# Switch to next checker mode
index = texture_modes.index(mode_max_count[0][0])
if texture_modes[ index ] != 'NONE' and len(image_sizes_x) > 1 or len(image_sizes_y) > 1:
# There are multiple resolutions on selected objects
mode = texture_modes[ index ]
elif texture_modes[ index ] != 'NONE' and (len(image_sizes_x) > 0 and image_sizes_x[0] != size_x) and (len(image_sizes_y) > 0 and image_sizes_y[0] != size_y):
# The selected objects have a different resolution
mode = texture_modes[ index ]
else:
# Next mode
mode = texture_modes[ (index+1)%len(texture_modes) ]
print("Mode: "+mode)
if mode == 'NONE':
for obj in objects:
remove_material(obj)
elif mode == 'GRAVITY':
image = load_image("checker_map_gravity")
for obj in objects:
apply_image(obj, image)
else:
name = utilities_texel.get_checker_name(mode, size_x, size_y)
image = get_image(name, mode, size_x, size_y)
for obj in objects:
apply_image(obj, image)
# Restore object selection
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='DESELECT')
for obj in objects:
obj.select_set( state = True, view_layer = None)
# Force Object mode
if bpy.context.view_layer.objects.active != None and bpy.context.object.mode != 'OBJECT':
bpy.ops.object.mode_set(mode='OBJECT')
# Collect Objects
objects = get_valid_objects()
if len(objects) == 0:
self.report({'ERROR_INVALID_INPUT'}, "No UV mapped objects selected" )
#Change View mode to TEXTURED
for area in bpy.context.screen.areas:
if area.type == 'VIEW_3D':
for space in area.spaces:
if space.type == 'VIEW_3D':
space.shading.type = 'MATERIAL'
if len(objects) > 0:
# Detect current Checker modes
mode_count = {}
for mode in texture_modes:
mode_count[mode] = 0
# Image sizes
image_sizes_x = []
image_sizes_y = []
# Collect current modes in selected objects
for obj in objects:
image = utilities_texel.get_object_texture_image(obj)
mode = 'NONE'
if image:
if "GRAVITY" in image.name.upper():
mode = 'GRAVITY'
elif image.generated_type in texture_modes:
# Generated checker maps
mode = image.generated_type
# Track image sizes
if image.size[0] not in image_sizes_x:
image_sizes_x.append(image.size[0])
if image.size[1] not in image_sizes_y:
image_sizes_y.append(image.size[1])
mode_count[mode]+=1
# Sort by count (returns tuple list of key,value)
mode_max_count = sorted(mode_count.items(), key=operator.itemgetter(1))
mode_max_count.reverse()
for key,val in mode_max_count:
print("{} = {}".format(key, val))
# Determine next mode
mode = 'NONE'
if mode_max_count[0][1] == 0:
# There are no maps
mode = texture_modes[0]
elif mode_max_count[0][0] in texture_modes:
if mode_max_count[-1][1] > 0:
# There is more than 0 of another mode, complete existing mode first
mode = mode_max_count[0][0]
else:
# Switch to next checker mode
index = texture_modes.index(mode_max_count[0][0])
if texture_modes[ index ] != 'NONE' and len(image_sizes_x) > 1 or len(image_sizes_y) > 1:
# There are multiple resolutions on selected objects
mode = texture_modes[ index ]
elif texture_modes[ index ] != 'NONE' and (len(image_sizes_x) > 0 and image_sizes_x[0] != size_x) and (len(image_sizes_y) > 0 and image_sizes_y[0] != size_y):
# The selected objects have a different resolution
mode = texture_modes[ index ]
else:
# Next mode
mode = texture_modes[ (index+1)%len(texture_modes) ]
print("Mode: "+mode)
if mode == 'NONE':
for obj in objects:
remove_material(obj)
elif mode == 'GRAVITY':
image = load_image("checker_map_gravity")
for obj in objects:
apply_image(obj, image)
else:
name = utilities_texel.get_checker_name(mode, size_x, size_y)
image = get_image(name, mode, size_x, size_y)
for obj in objects:
apply_image(obj, image)
# Restore object selection
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='DESELECT')
for obj in objects:
obj.select_set( state = True, view_layer = None)
# Clean up images and materials
utilities_texel.checker_images_cleanup()
# Clean up images and materials
utilities_texel.checker_images_cleanup()
# Force redraw of viewport to update texture
# bpy.context.scene.update()
bpy.context.view_layer.update()
# Force redraw of viewport to update texture
# bpy.context.scene.update()
bpy.context.view_layer.update()
def load_image(name):
pathTexture = icons_dir = os.path.join(os.path.dirname(__file__), "resources/{}.png".format(name))
image = bpy.ops.image.open(filepath=pathTexture, relative_path=False)
if "{}.png".format(name) in bpy.data.images:
bpy.data.images["{}.png".format(name)].name = name #remove extension in name
return bpy.data.images[name];
pathTexture = icons_dir = os.path.join(os.path.dirname(__file__), "resources/{}.png".format(name))
image = bpy.ops.image.open(filepath=pathTexture, relative_path=False)
if "{}.png".format(name) in bpy.data.images:
bpy.data.images["{}.png".format(name)].name = name #remove extension in name
return bpy.data.images[name];
def get_valid_objects():
# Collect Objects
objects = []
for obj in bpy.context.selected_objects:
if obj.type == 'MESH' and obj.data.uv_layers:
objects.append(obj)
# Collect Objects
objects = []
for obj in bpy.context.selected_objects:
if obj.type == 'MESH' and obj.data.uv_layers:
objects.append(obj)
return objects
return objects
@ -179,72 +179,72 @@ def get_valid_objects():
def remove_material(obj):
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='DESELECT')
obj.select_set( state = True, view_layer = None)
bpy.context.view_layer.objects.active = obj
count = len(obj.material_slots)
for i in range(count):
bpy.ops.object.material_slot_remove()
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='DESELECT')
obj.select_set( state = True, view_layer = None)
bpy.context.view_layer.objects.active = obj
count = len(obj.material_slots)
for i in range(count):
bpy.ops.object.material_slot_remove()
def apply_image(obj, image):
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='DESELECT')
obj.select_set( state = True, view_layer = None)
bpy.context.view_layer.objects.active = obj
# Assign Cycles material with image
# Get Material
material = None
if image.name in bpy.data.materials:
material = bpy.data.materials[image.name]
else:
material = bpy.data.materials.new(image.name)
material.use_nodes = True
# Assign material
if len(obj.data.materials) > 0:
obj.data.materials[0] = material
else:
obj.data.materials.append(material)
# Setup Node
tree = material.node_tree
node = None
if "checker" in tree.nodes:
node = tree.nodes["checker"]
else:
node = tree.nodes.new("ShaderNodeTexImage")
node.name = "checker"
node.select = True
tree.nodes.active = node
node.image = image
# LINKANDO:
tree = obj.data.materials[0].node_tree
links = tree.links
nodo1 = tree.nodes['checker']
nodo2 = tree.nodes['Principled BSDF']
links.new(nodo1.outputs['Color'], nodo2.inputs['Base Color'])
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='DESELECT')
obj.select_set( state = True, view_layer = None)
bpy.context.view_layer.objects.active = obj
# Assign Cycles material with image
# Get Material
material = None
if image.name in bpy.data.materials:
material = bpy.data.materials[image.name]
else:
material = bpy.data.materials.new(image.name)
material.use_nodes = True
# Assign material
if len(obj.data.materials) > 0:
obj.data.materials[0] = material
else:
obj.data.materials.append(material)
# Setup Node
tree = material.node_tree
node = None
if "checker" in tree.nodes:
node = tree.nodes["checker"]
else:
node = tree.nodes.new("ShaderNodeTexImage")
node.name = "checker"
node.select = True
tree.nodes.active = node
node.image = image
# LINKANDO:
tree = obj.data.materials[0].node_tree
links = tree.links
nodo1 = tree.nodes['checker']
nodo2 = tree.nodes['Principled BSDF']
links.new(nodo1.outputs['Color'], nodo2.inputs['Base Color'])
def get_image(name, mode, size_x, size_y):
# Image already exists?
if name in bpy.data.images:
# Update texture UV checker mode
bpy.data.images[name].generated_type = mode
return bpy.data.images[name];
# Create new image instead
image = bpy.data.images.new(name, width=size_x, height=size_y)
image.generated_type = mode #UV_GRID or COLOR_GRID
image.generated_width = int(size_x)
image.generated_height = int(size_y)
return image
# Image already exists?
if name in bpy.data.images:
# Update texture UV checker mode
bpy.data.images[name].generated_type = mode
return bpy.data.images[name];
# Create new image instead
image = bpy.data.images.new(name, width=size_x, height=size_y)
image.generated_type = mode #UV_GRID or COLOR_GRID
image.generated_width = int(size_x)
image.generated_height = int(size_y)
return image
bpy.utils.register_class(op)

@ -7,127 +7,127 @@ from . import utilities_texel
class op(bpy.types.Operator):
bl_idname = "uv.textools_texel_density_get"
bl_label = "Get Texel size"
bl_description = "Get Pixel per unit ratio or Texel density"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
#Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR':
return False
bl_idname = "uv.textools_texel_density_get"
bl_label = "Get Texel size"
bl_description = "Get Pixel per unit ratio or Texel density"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
#Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR':
return False
if not bpy.context.active_object:
return False
if len(bpy.context.selected_objects) == 0:
return False
if not bpy.context.active_object:
return False
if len(bpy.context.selected_objects) == 0:
return False
if bpy.context.active_object.type != 'MESH':
return False
if bpy.context.active_object.type != 'MESH':
return False
if not bpy.context.object.data.uv_layers:
return False
if not bpy.context.object.data.uv_layers:
return False
# if bpy.context.object.mode == 'EDIT':
# # In edit mode requires face select mode
# if bpy.context.scene.tool_settings.mesh_select_mode[2] == False:
# return False
# if bpy.context.object.mode == 'EDIT':
# # In edit mode requires face select mode
# if bpy.context.scene.tool_settings.mesh_select_mode[2] == False:
# return False
return True
return True
def execute(self, context):
get_texel_density(
self,
context
)
return {'FINISHED'}
def execute(self, context):
get_texel_density(
self,
context
)
return {'FINISHED'}
def get_texel_density(self, context):
print("Get texel density")
edit_mode = bpy.context.object.mode == 'EDIT'
object_faces = utilities_texel.get_selected_object_faces()
# Warning: No valid input objects
if len(object_faces) == 0:
self.report({'ERROR_INVALID_INPUT'}, "No UV maps or meshes selected" )
return
print("obj faces groups {}".format(len(object_faces)))
# Collect Images / textures
object_images = {}
for obj in object_faces:
image = utilities_texel.get_object_texture_image(obj)
if image:
object_images[obj] = image
# Warning: No valid images
if len(object_images) == 0:
self.report({'ERROR_INVALID_INPUT'}, "No Texture found. Assign Checker map or texture first." )
return
sum_area_vt = 0
sum_area_uv = 0
# Get area for each triangle in view and UV
for obj in object_faces:
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='DESELECT')
bpy.context.view_layer.objects.active = obj
obj.select_set( state = True, view_layer = None)
# Find image of object
image = object_images[obj]
if image:
bpy.ops.object.mode_set(mode='EDIT')
bm = bmesh.from_edit_mesh(obj.data)
uv_layers = bm.loops.layers.uv.verify()
bm.faces.ensure_lookup_table()
for index in object_faces[obj]:
face = bm.faces[index]
# Triangle Verts
triangle_uv = [loop[uv_layers].uv for loop in face.loops ]
triangle_vt = [vert.co for vert in face.verts]
#Triangle Areas
face_area_vt = utilities_texel.get_area_triangle(
triangle_vt[0],
triangle_vt[1],
triangle_vt[2]
)
face_area_uv = utilities_texel.get_area_triangle_uv(
triangle_uv[0],
triangle_uv[1],
triangle_uv[2],
image.size[0],
image.size[1]
)
sum_area_vt+= math.sqrt( face_area_vt )
sum_area_uv+= math.sqrt( face_area_uv ) * min(image.size[0], image.size[1])
# Restore selection
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='DESELECT')
for obj in object_faces:
obj.select_set( state = True, view_layer = None)
bpy.context.view_layer.objects.active = list(object_faces.keys())[0]
if edit_mode:
bpy.ops.object.mode_set(mode='EDIT')
# print("Sum verts area {}".format(sum_area_vt))
# print("Sum texture area {}".format(sum_area_uv))
if sum_area_uv == 0 or sum_area_vt == 0:
bpy.context.scene.texToolsSettings.texel_density = 0
else:
bpy.context.scene.texToolsSettings.texel_density = sum_area_uv / sum_area_vt
print("Get texel density")
edit_mode = bpy.context.object.mode == 'EDIT'
object_faces = utilities_texel.get_selected_object_faces()
# Warning: No valid input objects
if len(object_faces) == 0:
self.report({'ERROR_INVALID_INPUT'}, "No UV maps or meshes selected" )
return
print("obj faces groups {}".format(len(object_faces)))
# Collect Images / textures
object_images = {}
for obj in object_faces:
image = utilities_texel.get_object_texture_image(obj)
if image:
object_images[obj] = image
# Warning: No valid images
if len(object_images) == 0:
self.report({'ERROR_INVALID_INPUT'}, "No Texture found. Assign Checker map or texture first." )
return
sum_area_vt = 0
sum_area_uv = 0
# Get area for each triangle in view and UV
for obj in object_faces:
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='DESELECT')
bpy.context.view_layer.objects.active = obj
obj.select_set( state = True, view_layer = None)
# Find image of object
image = object_images[obj]
if image:
bpy.ops.object.mode_set(mode='EDIT')
bm = bmesh.from_edit_mesh(obj.data)
uv_layers = bm.loops.layers.uv.verify()
bm.faces.ensure_lookup_table()
for index in object_faces[obj]:
face = bm.faces[index]
# Triangle Verts
triangle_uv = [loop[uv_layers].uv for loop in face.loops ]
triangle_vt = [vert.co for vert in face.verts]
#Triangle Areas
face_area_vt = utilities_texel.get_area_triangle(
triangle_vt[0],
triangle_vt[1],
triangle_vt[2]
)
face_area_uv = utilities_texel.get_area_triangle_uv(
triangle_uv[0],
triangle_uv[1],
triangle_uv[2],
image.size[0],
image.size[1]
)
sum_area_vt+= math.sqrt( face_area_vt )
sum_area_uv+= math.sqrt( face_area_uv ) * min(image.size[0], image.size[1])
# Restore selection
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='DESELECT')
for obj in object_faces:
obj.select_set( state = True, view_layer = None)
bpy.context.view_layer.objects.active = list(object_faces.keys())[0]
if edit_mode:
bpy.ops.object.mode_set(mode='EDIT')
# print("Sum verts area {}".format(sum_area_vt))
# print("Sum texture area {}".format(sum_area_uv))
if sum_area_uv == 0 or sum_area_vt == 0:
bpy.context.scene.texToolsSettings.texel_density = 0
else:
bpy.context.scene.texToolsSettings.texel_density = sum_area_uv / sum_area_vt
bpy.utils.register_class(op)

@ -10,180 +10,180 @@ from . import utilities_texel
from . import utilities_uv
class op(bpy.types.Operator):
bl_idname = "uv.textools_texel_density_set"
bl_label = "Set Texel size"
bl_description = "Apply texel density by scaling the UV's to match the ratio"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
if not bpy.context.active_object:
return False
if len(bpy.context.selected_objects) == 0:
return False
if bpy.context.active_object.type != 'MESH':
return False
#Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR':
return False
#Requires UV map
if not bpy.context.object.data.uv_layers:
return False
# if bpy.context.object.mode == 'EDIT':
# # In edit mode requires face select mode
# if bpy.context.scene.tool_settings.mesh_select_mode[2] == False:
# return False
return True
def execute(self, context):
set_texel_density(
self,
context,
bpy.context.scene.texToolsSettings.texel_mode_scale,
bpy.context.scene.texToolsSettings.texel_density
)
return {'FINISHED'}
bl_idname = "uv.textools_texel_density_set"
bl_label = "Set Texel size"
bl_description = "Apply texel density by scaling the UV's to match the ratio"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
if not bpy.context.active_object:
return False
if len(bpy.context.selected_objects) == 0:
return False
if bpy.context.active_object.type != 'MESH':
return False
#Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR':
return False
#Requires UV map
if not bpy.context.object.data.uv_layers:
return False
# if bpy.context.object.mode == 'EDIT':
# # In edit mode requires face select mode
# if bpy.context.scene.tool_settings.mesh_select_mode[2] == False:
# return False
return True
def execute(self, context):
set_texel_density(
self,
context,
bpy.context.scene.texToolsSettings.texel_mode_scale,
bpy.context.scene.texToolsSettings.texel_density
)
return {'FINISHED'}
def set_texel_density(self, context, mode, density):
print("Set texel density!")
is_edit = bpy.context.object.mode == 'EDIT'
is_sync = bpy.context.scene.tool_settings.use_uv_select_sync
object_faces = utilities_texel.get_selected_object_faces()
# Warning: No valid input objects
if len(object_faces) == 0:
self.report({'ERROR_INVALID_INPUT'}, "No valid meshes or UV maps" )
return
# Collect Images / textures
object_images = {}
for obj in object_faces:
image = utilities_texel.get_object_texture_image(obj)
if image:
object_images[obj] = image
# Warning: No valid images
if len(object_images) == 0:
self.report({'ERROR_INVALID_INPUT'}, "No Texture found. Assign Checker map or texture." )
return
for obj in object_faces:
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='DESELECT')
bpy.context.view_layer.objects.active = obj
obj.select_set( state = True, view_layer = None)
# Find image of object
image = object_images[obj]
if image:
bpy.ops.object.mode_set(mode='EDIT')
bpy.context.scene.tool_settings.use_uv_select_sync = False
# Store selection
utilities_uv.selection_store()
bm = bmesh.from_edit_mesh(obj.data)
uv_layers = bm.loops.layers.uv.verify()
# Collect groups of faces to scale together
group_faces = []
if is_edit:
# Collect selected faces as islands
bm.faces.ensure_lookup_table()
bpy.ops.uv.select_all(action='SELECT')
group_faces = utilities_uv.getSelectionIslands()
elif mode == 'ALL':
# Scale all UV's together
group_faces = [bm.faces]
elif mode == 'ISLAND':
# Scale each UV idland centered
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.uv.select_all(action='SELECT')
group_faces = utilities_uv.getSelectionIslands()
print("group_faces {}x".format(len(group_faces)))
for group in group_faces:
# Get triangle areas
sum_area_vt = 0
sum_area_uv = 0
for face in group:
# Triangle Verts
triangle_uv = [loop[uv_layers].uv for loop in face.loops ]
triangle_vt = [obj.matrix_world @ vert.co for vert in face.verts]
#Triangle Areas
face_area_vt = utilities_texel.get_area_triangle(
triangle_vt[0],
triangle_vt[1],
triangle_vt[2]
)
face_area_uv = utilities_texel.get_area_triangle_uv(
triangle_uv[0],
triangle_uv[1],
triangle_uv[2],
image.size[0],
image.size[1]
)
sum_area_vt+= math.sqrt( face_area_vt )
sum_area_uv+= math.sqrt( face_area_uv ) * min(image.size[0], image.size[1])
# Apply scale to group
print("scale: {:.2f} {:.2f} {:.2f} ".format(density, sum_area_uv, sum_area_vt))
scale = 0
if density > 0 and sum_area_uv > 0 and sum_area_vt > 0:
scale = density / (sum_area_uv / sum_area_vt)
# Set Scale Origin to Island or top left
if mode == 'ISLAND':
bpy.context.tool_settings.transform_pivot_point = 'MEDIAN_POINT'
elif mode == 'ALL':
bpy.context.tool_settings.transform_pivot_point = 'CURSOR'
bpy.ops.uv.cursor_set(location=(0, 1))
# Select Face loops and scale
bpy.ops.uv.select_all(action='DESELECT')
bpy.context.scene.tool_settings.uv_select_mode = 'VERTEX'
for face in group:
for loop in face.loops:
loop[uv_layers].select = True
print("Scale: {} {}x".format(scale, len(group)))
bpy.ops.transform.resize(value=(scale, scale, 1), use_proportional_edit=False)
# Restore selection
utilities_uv.selection_restore()
# Restore selection
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='DESELECT')
for obj in object_faces:
obj.select_set( state = True, view_layer = None)
bpy.context.view_layer.objects.active = list(object_faces.keys())[0]
# Restore edit mode
if is_edit:
bpy.ops.object.mode_set(mode='EDIT')
# Restore sync mode
if is_sync:
bpy.context.scene.tool_settings.use_uv_select_sync = True
print("Set texel density!")
is_edit = bpy.context.object.mode == 'EDIT'
is_sync = bpy.context.scene.tool_settings.use_uv_select_sync
object_faces = utilities_texel.get_selected_object_faces()
# Warning: No valid input objects
if len(object_faces) == 0:
self.report({'ERROR_INVALID_INPUT'}, "No valid meshes or UV maps" )
return
# Collect Images / textures
object_images = {}
for obj in object_faces:
image = utilities_texel.get_object_texture_image(obj)
if image:
object_images[obj] = image
# Warning: No valid images
if len(object_images) == 0:
self.report({'ERROR_INVALID_INPUT'}, "No Texture found. Assign Checker map or texture." )
return
for obj in object_faces:
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='DESELECT')
bpy.context.view_layer.objects.active = obj
obj.select_set( state = True, view_layer = None)
# Find image of object
image = object_images[obj]
if image:
bpy.ops.object.mode_set(mode='EDIT')
bpy.context.scene.tool_settings.use_uv_select_sync = False
# Store selection
utilities_uv.selection_store()
bm = bmesh.from_edit_mesh(obj.data)
uv_layers = bm.loops.layers.uv.verify()
# Collect groups of faces to scale together
group_faces = []
if is_edit:
# Collect selected faces as islands
bm.faces.ensure_lookup_table()
bpy.ops.uv.select_all(action='SELECT')
group_faces = utilities_uv.getSelectionIslands()
elif mode == 'ALL':
# Scale all UV's together
group_faces = [bm.faces]
elif mode == 'ISLAND':
# Scale each UV idland centered
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.uv.select_all(action='SELECT')
group_faces = utilities_uv.getSelectionIslands()
print("group_faces {}x".format(len(group_faces)))
for group in group_faces:
# Get triangle areas
sum_area_vt = 0
sum_area_uv = 0
for face in group:
# Triangle Verts
triangle_uv = [loop[uv_layers].uv for loop in face.loops ]
triangle_vt = [obj.matrix_world @ vert.co for vert in face.verts]
#Triangle Areas
face_area_vt = utilities_texel.get_area_triangle(
triangle_vt[0],
triangle_vt[1],
triangle_vt[2]
)
face_area_uv = utilities_texel.get_area_triangle_uv(
triangle_uv[0],
triangle_uv[1],
triangle_uv[2],
image.size[0],
image.size[1]
)
sum_area_vt+= math.sqrt( face_area_vt )
sum_area_uv+= math.sqrt( face_area_uv ) * min(image.size[0], image.size[1])
# Apply scale to group
print("scale: {:.2f} {:.2f} {:.2f} ".format(density, sum_area_uv, sum_area_vt))
scale = 0
if density > 0 and sum_area_uv > 0 and sum_area_vt > 0:
scale = density / (sum_area_uv / sum_area_vt)
# Set Scale Origin to Island or top left
if mode == 'ISLAND':
bpy.context.tool_settings.transform_pivot_point = 'MEDIAN_POINT'
elif mode == 'ALL':
bpy.context.tool_settings.transform_pivot_point = 'CURSOR'
bpy.ops.uv.cursor_set(location=(0, 1))
# Select Face loops and scale
bpy.ops.uv.select_all(action='DESELECT')
bpy.context.scene.tool_settings.uv_select_mode = 'VERTEX'
for face in group:
for loop in face.loops:
loop[uv_layers].select = True
print("Scale: {} {}x".format(scale, len(group)))
bpy.ops.transform.resize(value=(scale, scale, 1), use_proportional_edit=False)
# Restore selection
utilities_uv.selection_restore()
# Restore selection
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='DESELECT')
for obj in object_faces:
obj.select_set( state = True, view_layer = None)
bpy.context.view_layer.objects.active = list(object_faces.keys())[0]
# Restore edit mode
if is_edit:
bpy.ops.object.mode_set(mode='EDIT')
# Restore sync mode
if is_sync:
bpy.context.scene.tool_settings.use_uv_select_sync = True
bpy.utils.register_class(op)

@ -9,42 +9,42 @@ from . import utilities_bake
class op(bpy.types.Operator):
bl_idname = "uv.textools_texture_open"
bl_label = "Open Texture"
bl_description = "Open the texture on the system"
bl_idname = "uv.textools_texture_open"
bl_label = "Open Texture"
bl_description = "Open the texture on the system"
name : bpy.props.StringProperty(
name="image name",
default = ""
)
name : bpy.props.StringProperty(
name="image name",
default = ""
)
@classmethod
def poll(cls, context):
return True
def execute(self, context):
open_texture(self, context)
return {'FINISHED'}
@classmethod
def poll(cls, context):
return True
def execute(self, context):
open_texture(self, context)
return {'FINISHED'}
def open_texture(self, context):
print("Info")
if self.name in bpy.data.images:
image = bpy.data.images[self.name]
if image.filepath != "":
path = bpy.path.abspath(image.filepath)
# https://meshlogic.github.io/posts/blender/addons/extra-image-list/
# https://docs.blender.org/api/blender_python_api_2_78_release/bpy.ops.image.html
print("Open: {}".format(path))
if sys.platform == "win32":
os.startfile(path)
else:
opener ="open" if sys.platform == "darwin" else "xdg-open"
subprocess.call([opener, path])
print("Info")
if self.name in bpy.data.images:
image = bpy.data.images[self.name]
if image.filepath != "":
path = bpy.path.abspath(image.filepath)
# https://meshlogic.github.io/posts/blender/addons/extra-image-list/
# https://docs.blender.org/api/blender_python_api_2_78_release/bpy.ops.image.html
print("Open: {}".format(path))
if sys.platform == "win32":
os.startfile(path)
else:
opener ="open" if sys.platform == "darwin" else "xdg-open"
subprocess.call([opener, path])
bpy.utils.register_class(op)

@ -13,89 +13,89 @@ material_prefix = "TT_atlas_"
gamma = 2.2
class op(bpy.types.Operator):
bl_idname = "uv.textools_texture_preview"
bl_label = "Preview Texture"
bl_description = "Preview the current UV image view background image on the selected object."
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
if not bpy.context.active_object:
return False
if len(settings.sets) == 0:
return False
# Only when we have a background image
for area in bpy.context.screen.areas:
if area.type == 'IMAGE_EDITOR':
return area.spaces[0].image
return False
def execute(self, context):
print("PREVIEW TEXTURE????")
preview_texture(self, context)
return {'FINISHED'}
bl_idname = "uv.textools_texture_preview"
bl_label = "Preview Texture"
bl_description = "Preview the current UV image view background image on the selected object."
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
if not bpy.context.active_object:
return False
if len(settings.sets) == 0:
return False
# Only when we have a background image
for area in bpy.context.screen.areas:
if area.type == 'IMAGE_EDITOR':
return area.spaces[0].image
return False
def execute(self, context):
print("PREVIEW TEXTURE????")
preview_texture(self, context)
return {'FINISHED'}
def preview_texture(self, context):
# Collect all low objects from bake sets
objects = [obj for s in settings.sets for obj in s.objects_low if obj.data.uv_layers]
# Get view 3D area
view_area = None
for area in bpy.context.screen.areas:
if area.type == 'VIEW_3D':
view_area = area
# Exit existing local view
# if view_area and view_area.spaces[0].local_view:
# bpy.ops.view3d.localview({'area': view_area})
# return
# Get background image
image = None
for area in bpy.context.screen.areas:
if area.type == 'IMAGE_EDITOR':
image = area.spaces[0].image
break
if image:
for obj in objects:
print("Map {}".format(obj.name))
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='DESELECT')
obj.select_set( state = True, view_layer = None)
bpy.context.view_layer.objects.active = obj
for i in range(len(obj.material_slots)):
bpy.ops.object.material_slot_remove()
#Create material with image
bpy.ops.object.material_slot_add()
obj.material_slots[0].material = utilities_bake.get_image_material(image)
obj.display_type = 'TEXTURED'
# Re-Select objects
bpy.ops.object.select_all(action='DESELECT')
for obj in objects:
obj.select_set( state = True, view_layer = None)
if view_area:
#Change View mode to TEXTURED
for space in view_area.spaces:
if space.type == 'VIEW_3D':
space.shading.type = 'MATERIAL'
# Enter local view
# bpy.ops.view3d.localview({'area': view_area})
# bpy.ops.ui.textools_popup('INVOKE_DEFAULT', message="Object is in isolated view")
# Collect all low objects from bake sets
objects = [obj for s in settings.sets for obj in s.objects_low if obj.data.uv_layers]
# Get view 3D area
view_area = None
for area in bpy.context.screen.areas:
if area.type == 'VIEW_3D':
view_area = area
# Exit existing local view
# if view_area and view_area.spaces[0].local_view:
# bpy.ops.view3d.localview({'area': view_area})
# return
# Get background image
image = None
for area in bpy.context.screen.areas:
if area.type == 'IMAGE_EDITOR':
image = area.spaces[0].image
break
if image:
for obj in objects:
print("Map {}".format(obj.name))
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='DESELECT')
obj.select_set( state = True, view_layer = None)
bpy.context.view_layer.objects.active = obj
for i in range(len(obj.material_slots)):
bpy.ops.object.material_slot_remove()
#Create material with image
bpy.ops.object.material_slot_add()
obj.material_slots[0].material = utilities_bake.get_image_material(image)
obj.display_type = 'TEXTURED'
# Re-Select objects
bpy.ops.object.select_all(action='DESELECT')
for obj in objects:
obj.select_set( state = True, view_layer = None)
if view_area:
#Change View mode to TEXTURED
for space in view_area.spaces:
if space.type == 'VIEW_3D':
space.shading.type = 'MATERIAL'
# Enter local view
# bpy.ops.view3d.localview({'area': view_area})
# bpy.ops.ui.textools_popup('INVOKE_DEFAULT', message="Object is in isolated view")
bpy.utils.register_class(op)

@ -6,59 +6,59 @@ from collections import defaultdict
from math import pi
class op(bpy.types.Operator):
bl_idname = "uv.textools_texture_reload_all"
bl_label = "Reload Textures and remove unused Textures"
bl_description = "Reload all textures"
bl_idname = "uv.textools_texture_reload_all"
bl_label = "Reload Textures and remove unused Textures"
bl_description = "Reload all textures"
@classmethod
def poll(cls, context):
return True
@classmethod
def poll(cls, context):
return True
def execute(self, context):
main(context)
return {'FINISHED'}
def execute(self, context):
main(context)
return {'FINISHED'}
def main(context):
count_clear_mat = 0
count_clear_img = 0
count_reload = 0
count_clear_mat = 0
count_clear_img = 0
count_reload = 0
for material in bpy.data.materials:
if not material.users:
count_clear_mat+=1
material.user_clear()
bpy.data.materials.remove(material)
for material in bpy.data.materials:
if not material.users:
count_clear_mat+=1
material.user_clear()
bpy.data.materials.remove(material)
# Clean up unused images
for image in bpy.data.images:
if not image.users:
count_clear_img+=1
image.user_clear()
bpy.data.images.remove(image)
# Clean up unused images
for image in bpy.data.images:
if not image.users:
count_clear_img+=1
image.user_clear()
bpy.data.images.remove(image)
#Reload all File images
for img in bpy.data.images :
if img.source == 'FILE' :
count_reload+=1
img.reload()
#Reload all File images
for img in bpy.data.images :
if img.source == 'FILE' :
count_reload+=1
img.reload()
# Refresh vieport texture
for window in bpy.context.window_manager.windows:
screen = window.screen
for area in screen.areas:
area.tag_redraw()
# Refresh vieport texture
for window in bpy.context.window_manager.windows:
screen = window.screen
for area in screen.areas:
area.tag_redraw()
# Show popup on cleared & reloaded items
message = ""
if count_reload > 0:
message+="{}x reloaded. ".format(count_reload)
if count_clear_mat > 0:
message+="{}x mat cleared. ".format(count_clear_mat)
if count_clear_img > 0:
message+="{}x img cleared.".format(count_clear_img)
# Show popup on cleared & reloaded items
message = ""
if count_reload > 0:
message+="{}x reloaded. ".format(count_reload)
if count_clear_mat > 0:
message+="{}x mat cleared. ".format(count_clear_mat)
if count_clear_img > 0:
message+="{}x img cleared.".format(count_clear_img)
if len(message) > 0:
bpy.ops.ui.textools_popup('INVOKE_DEFAULT', message=message)
bpy.utils.register_class(op)
if len(message) > 0:
bpy.ops.ui.textools_popup('INVOKE_DEFAULT', message=message)
bpy.utils.register_class(op)

@ -10,34 +10,34 @@ from . import utilities_bake
class op(bpy.types.Operator):
bl_idname = "uv.textools_texture_remove"
bl_label = "Remove Texture"
bl_description = "Remove the texture"
bl_options = {'REGISTER', 'UNDO'}
bl_idname = "uv.textools_texture_remove"
bl_label = "Remove Texture"
bl_description = "Remove the texture"
bl_options = {'REGISTER', 'UNDO'}
name : bpy.props.StringProperty(
name="image name",
default = ""
)
name : bpy.props.StringProperty(
name="image name",
default = ""
)
@classmethod
def poll(cls, context):
return True
def execute(self, context):
remove_texture(self.name)
return {'FINISHED'}
@classmethod
def poll(cls, context):
return True
def execute(self, context):
remove_texture(self.name)
return {'FINISHED'}
def remove_texture(name):
print("Save image.. "+name)
print("Save image.. "+name)
if name in bpy.data.images:
bpy.data.images.remove( bpy.data.images[name] )
if name in bpy.data.images:
bpy.data.images.remove( bpy.data.images[name] )
bpy.utils.register_class(op)

@ -10,100 +10,100 @@ from . import utilities_bake
class op(bpy.types.Operator):
bl_idname = "uv.textools_texture_save"
bl_label = "Save Texture"
bl_description = "Save the texture"
bl_idname = "uv.textools_texture_save"
bl_label = "Save Texture"
bl_description = "Save the texture"
name : bpy.props.StringProperty(
name="image name",
default = ""
)
name : bpy.props.StringProperty(
name="image name",
default = ""
)
# Properties used by the file browser
# filepath = bpy.props.StringProperty(subtype="FILE_PATH")
# http://nullege.com/codes/show/src%40b%40l%40blenderpython-HEAD%40scripts%40addons_extern%40io_scene_valvesource%40import_smd.py/90/bpy.context.window_manager.fileselect_add/python
filepath : bpy.props.StringProperty(name="myName.png", description="Texture filepath", maxlen=1024, default="bla bla.png")
filter_folder : BoolProperty(name="Filter folders", description="", default=True, options={'HIDDEN'})
filter_glob : StringProperty(default="*.png;*.tga;*.jpg;*.tif;*.exr", options={'HIDDEN'})
# Properties used by the file browser
# filepath = bpy.props.StringProperty(subtype="FILE_PATH")
# http://nullege.com/codes/show/src%40b%40l%40blenderpython-HEAD%40scripts%40addons_extern%40io_scene_valvesource%40import_smd.py/90/bpy.context.window_manager.fileselect_add/python
filepath : bpy.props.StringProperty(name="myName.png", description="Texture filepath", maxlen=1024, default="bla bla.png")
filter_folder : BoolProperty(name="Filter folders", description="", default=True, options={'HIDDEN'})
filter_glob : StringProperty(default="*.png;*.tga;*.jpg;*.tif;*.exr", options={'HIDDEN'})
def invoke(self, context, event):
# if self.filepath == "":
# self.filepath = bpy.context.scene.FBXBundleSettings.path
# blend_filepath = context.blend_data.filepath
# https://blender.stackexchange.com/questions/6159/changing-default-text-value-in-file-dialogue
context.window_manager.fileselect_add(self)
return {'RUNNING_MODAL'}
def invoke(self, context, event):
# if self.filepath == "":
# self.filepath = bpy.context.scene.FBXBundleSettings.path
# blend_filepath = context.blend_data.filepath
# https://blender.stackexchange.com/questions/6159/changing-default-text-value-in-file-dialogue
context.window_manager.fileselect_add(self)
return {'RUNNING_MODAL'}
def draw(self, context):
layout = self.layout
def draw(self, context):
layout = self.layout
layout.label(text="Choose your Unity Asset directory")
layout.label(text="Choose your Unity Asset directory")
@classmethod
def poll(cls, context):
return True
@classmethod
def poll(cls, context):
return True
def execute(self, context):
save_texture(self.filepath)
return {'FINISHED'}
def execute(self, context):
save_texture(self.filepath)
return {'FINISHED'}
def save_texture(path):
print("Save image.. "+path)
print("Save image.. "+path)
# class op(bpy.types.Operator):
# bl_idname = "uv.textools_texture_save"
# bl_label = "Save Texture"
# bl_description = "Save the texture"
# bl_idname = "uv.textools_texture_save"
# bl_label = "Save Texture"
# bl_description = "Save the texture"
# name = bpy.props.StringProperty(
# name="image name",
# default = ""
# )
# name = bpy.props.StringProperty(
# name="image name",
# default = ""
# )
# @classmethod
# def poll(cls, context):
# return True
# def execute(self, context):
# save_texture(self, context)
# return {'FINISHED'}
# @classmethod
# def poll(cls, context):
# return True
# def execute(self, context):
# save_texture(self, context)
# return {'FINISHED'}
'''
class op_ui_image_save(bpy.types.Operator):
bl_idname = "uv.textools_ui_image_save"
bl_label = "Save image"
bl_description = "Save this image"
image_name = bpy.props.StringProperty(
name="image name",
default = ""
)
@classmethod
def poll(cls, context):
return True
def execute(self, context):
# bpy.context.scene.tool_settings.use_uv_select_sync = False
# bpy.ops.mesh.select_all(action='SELECT')
print("Saving image {}".format(self.image_name))
# bpy.ops.image.save_as()
return {'FINISHED'}
bl_idname = "uv.textools_ui_image_save"
bl_label = "Save image"
bl_description = "Save this image"
image_name = bpy.props.StringProperty(
name="image name",
default = ""
)
@classmethod
def poll(cls, context):
return True
def execute(self, context):
# bpy.context.scene.tool_settings.use_uv_select_sync = False
# bpy.ops.mesh.select_all(action='SELECT')
print("Saving image {}".format(self.image_name))
# bpy.ops.image.save_as()
return {'FINISHED'}
'''
bpy.utils.register_class(op)

@ -8,77 +8,77 @@ from . import utilities_bake
from . import op_bake
class op(bpy.types.Operator):
bl_idname = "uv.textools_texture_select"
bl_label = "Select Texture"
bl_description = "Select the texture and bake mode"
bl_options = {'REGISTER', 'UNDO'}
bl_idname = "uv.textools_texture_select"
bl_label = "Select Texture"
bl_description = "Select the texture and bake mode"
bl_options = {'REGISTER', 'UNDO'}
name : bpy.props.StringProperty(
name="image name",
default = ""
)
name : bpy.props.StringProperty(
name="image name",
default = ""
)
@classmethod
def poll(cls, context):
return True
def execute(self, context):
select_texture(self, context)
return {'FINISHED'}
@classmethod
def poll(cls, context):
return True
def execute(self, context):
select_texture(self, context)
return {'FINISHED'}
def select_texture(self, context):
print("Select "+self.name)
# Set bake mode
for mode in op_bake.modes:
if mode in self.name:
print("Found mode: "+mode)
prop = bpy.context.scene.bl_rna.properties["TT_bake_mode"]
enum_values = [e.identifier for e in prop.enum_items]
# find matching enum
for key in enum_values:
print("TT_bake "+key)
if mode in key:
print("set m: "+key)
bpy.context.scene.TT_bake_mode = key
break;
break
# Set background image
if self.name in bpy.data.images:
image = bpy.data.images[self.name]
for area in bpy.context.screen.areas:
if area.type == 'IMAGE_EDITOR':
area.spaces[0].image = image
print("Select "+self.name)
# Set bake mode
for mode in op_bake.modes:
if mode in self.name:
print("Found mode: "+mode)
prop = bpy.context.scene.bl_rna.properties["TT_bake_mode"]
enum_values = [e.identifier for e in prop.enum_items]
# find matching enum
for key in enum_values:
print("TT_bake "+key)
if mode in key:
print("set m: "+key)
bpy.context.scene.TT_bake_mode = key
break;
break
# Set background image
if self.name in bpy.data.images:
image = bpy.data.images[self.name]
for area in bpy.context.screen.areas:
if area.type == 'IMAGE_EDITOR':
area.spaces[0].image = image
'''
class op_ui_image_select(bpy.types.Operator):
bl_idname = "uv.textools_ui_image_select"
bl_label = "Select image"
bl_description = "Select this image"
image_name = bpy.props.StringProperty(
name="image name",
default = ""
)
@classmethod
def poll(cls, context):
return True
def execute(self, context):
# bpy.context.scene.tool_settings.use_uv_select_sync = False
# bpy.ops.mesh.select_all(action='SELECT')
print("Select image {}".format(self.image_name))
# bpy.ops.image.save_as()
return {'FINISHED'}
bl_idname = "uv.textools_ui_image_select"
bl_label = "Select image"
bl_description = "Select this image"
image_name = bpy.props.StringProperty(
name="image name",
default = ""
)
@classmethod
def poll(cls, context):
return True
def execute(self, context):
# bpy.context.scene.tool_settings.use_uv_select_sync = False
# bpy.ops.mesh.select_all(action='SELECT')
print("Select image {}".format(self.image_name))
# bpy.ops.image.save_as()
return {'FINISHED'}
'''
bpy.utils.register_class(op)

@ -9,87 +9,87 @@ from . import utilities_uv
from . import utilities_ui
class op(bpy.types.Operator):
bl_idname = "uv.textools_unwrap_edge_peel"
bl_label = "Peel Edge"
bl_description = "Unwrap pipe along selected edges"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
bl_idname = "uv.textools_unwrap_edge_peel"
bl_label = "Peel Edge"
bl_description = "Unwrap pipe along selected edges"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
if not bpy.context.active_object:
return False
if not bpy.context.active_object:
return False
if bpy.context.active_object.type != 'MESH':
return False
if bpy.context.active_object.type != 'MESH':
return False
#Only in Edit mode
if bpy.context.active_object.mode != 'EDIT':
return False
#Only in Edit mode
if bpy.context.active_object.mode != 'EDIT':
return False
# Need view Face mode
if tuple(bpy.context.scene.tool_settings.mesh_select_mode)[1] == False:
return False
# Need view Face mode
if tuple(bpy.context.scene.tool_settings.mesh_select_mode)[1] == False:
return False
return True
return True
def execute(self, context):
unwrap_edges_pipe(self, context)
return {'FINISHED'}
def execute(self, context):
unwrap_edges_pipe(self, context)
return {'FINISHED'}
def unwrap_edges_pipe(self, context):
bm = bmesh.from_edit_mesh(bpy.context.active_object.data)
uv_layers = bm.loops.layers.uv.verify()
contextViewUV = utilities_ui.GetContextViewUV()
if not contextViewUV:
self.report({'ERROR_INVALID_INPUT'}, "This tool requires an available UV/Image view.")
return
# selected_initial = [edge for edge in bm.edges if edge.select]
selected_edges = []
selected_faces = []
# Extend loop selection
bpy.ops.mesh.loop_multi_select(ring=False)
selected_edges = [edge for edge in bm.edges if edge.select]
if len(selected_edges) == 0:
self.report({'ERROR_INVALID_INPUT'}, "No edges selected in the view" )
return
# Convert linked selection to single UV island
bpy.ops.mesh.select_linked(delimit=set())
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE')
bpy.ops.uv.textools_unwrap_faces_iron()
selected_faces = [face for face in bm.faces if face.select]
if len(selected_faces) == 0:
self.report({'ERROR_INVALID_INPUT'}, "No faces available" )
return
# Mark previous selected edges as Seam
bpy.ops.mesh.select_all(action='DESELECT')
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE')
for edge in selected_edges:
edge.select = True
bpy.ops.mesh.mark_seam(clear=False)
# Follow active quad unwrap for faces
bpy.ops.mesh.select_all(action='DESELECT')
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE')
for face in selected_faces:
face.select = True
bm.faces.active = selected_faces[0]
bpy.ops.uv.unwrap(method='ANGLE_BASED', margin=0.0226216)
bpy.ops.uv.select_all(action='SELECT')
bpy.ops.uv.textools_rectify(contextViewUV)
# TODO: Restore initial selection
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE')
bm = bmesh.from_edit_mesh(bpy.context.active_object.data)
uv_layers = bm.loops.layers.uv.verify()
contextViewUV = utilities_ui.GetContextViewUV()
if not contextViewUV:
self.report({'ERROR_INVALID_INPUT'}, "This tool requires an available UV/Image view.")
return
# selected_initial = [edge for edge in bm.edges if edge.select]
selected_edges = []
selected_faces = []
# Extend loop selection
bpy.ops.mesh.loop_multi_select(ring=False)
selected_edges = [edge for edge in bm.edges if edge.select]
if len(selected_edges) == 0:
self.report({'ERROR_INVALID_INPUT'}, "No edges selected in the view" )
return
# Convert linked selection to single UV island
bpy.ops.mesh.select_linked(delimit=set())
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE')
bpy.ops.uv.textools_unwrap_faces_iron()
selected_faces = [face for face in bm.faces if face.select]
if len(selected_faces) == 0:
self.report({'ERROR_INVALID_INPUT'}, "No faces available" )
return
# Mark previous selected edges as Seam
bpy.ops.mesh.select_all(action='DESELECT')
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE')
for edge in selected_edges:
edge.select = True
bpy.ops.mesh.mark_seam(clear=False)
# Follow active quad unwrap for faces
bpy.ops.mesh.select_all(action='DESELECT')
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE')
for face in selected_faces:
face.select = True
bm.faces.active = selected_faces[0]
bpy.ops.uv.unwrap(method='ANGLE_BASED', margin=0.0226216)
bpy.ops.uv.select_all(action='SELECT')
bpy.ops.uv.textools_rectify(contextViewUV)
# TODO: Restore initial selection
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE')
bpy.utils.register_class(op)

@ -8,69 +8,69 @@ from math import pi
from . import utilities_uv
class op(bpy.types.Operator):
"""UV Operator description"""
bl_idname = "uv.textools_unwrap_faces_iron"
bl_label = "Iron"
bl_description = "Unwrap selected faces into a single UV island"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
"""UV Operator description"""
bl_idname = "uv.textools_unwrap_faces_iron"
bl_label = "Iron"
bl_description = "Unwrap selected faces into a single UV island"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
if not bpy.context.active_object:
return False
if not bpy.context.active_object:
return False
if bpy.context.active_object.type != 'MESH':
return False
if bpy.context.active_object.type != 'MESH':
return False
#Only in Edit mode
if bpy.context.active_object.mode != 'EDIT':
return False
#Only in Edit mode
if bpy.context.active_object.mode != 'EDIT':
return False
# Need view Face mode
if tuple(bpy.context.scene.tool_settings.mesh_select_mode)[2] == False:
return False
#Only in UV editor mode
# if bpy.context.area.type != 'IMAGE_EDITOR':
# return False
# Need view Face mode
if tuple(bpy.context.scene.tool_settings.mesh_select_mode)[2] == False:
return False
#Only in UV editor mode
# if bpy.context.area.type != 'IMAGE_EDITOR':
# return False
#Requires UV map
# if not bpy.context.object.data.uv_layers:
# return False
#Requires UV map
# if not bpy.context.object.data.uv_layers:
# return False
# if bpy.context.scene.tool_settings.uv_select_mode != 'FACE':
# return False
return True
# if bpy.context.scene.tool_settings.uv_select_mode != 'FACE':
# return False
return True
def execute(self, context):
main(context)
return {'FINISHED'}
def execute(self, context):
main(context)
return {'FINISHED'}
def main(context):
print("operatyor_faces_iron()")
print("operatyor_faces_iron()")
#Store selection
utilities_uv.selection_store()
#Store selection
utilities_uv.selection_store()
bm = bmesh.from_edit_mesh(bpy.context.active_object.data)
uv_layers = bm.loops.layers.uv.verify()
bm = bmesh.from_edit_mesh(bpy.context.active_object.data)
uv_layers = bm.loops.layers.uv.verify()
bpy.context.scene.tool_settings.uv_select_mode = 'FACE'
bpy.ops.mesh.mark_seam(clear=True)
bpy.context.scene.tool_settings.uv_select_mode = 'FACE'
bpy.ops.mesh.mark_seam(clear=True)
selected_faces = [f for f in bm.faces if f.select]
selected_faces = [f for f in bm.faces if f.select]
# Hard edges to seams
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE')
bpy.ops.mesh.region_to_loop()
bpy.ops.mesh.mark_seam(clear=False)
# Hard edges to seams
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE')
bpy.ops.mesh.region_to_loop()
bpy.ops.mesh.mark_seam(clear=False)
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE')
for face in selected_faces:
face.select = True
bpy.ops.uv.unwrap(method='ANGLE_BASED', margin=0.0226216)
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE')
for face in selected_faces:
face.select = True
bpy.ops.uv.unwrap(method='ANGLE_BASED', margin=0.0226216)
bpy.utils.register_class(op)

@ -8,60 +8,60 @@ from math import pi
from . import utilities_ui
class op(bpy.types.Operator):
bl_idname = "uv.textools_uv_channel_add"
bl_label = "Add UV Channel"
bl_description = "Add a new UV channel with smart UV projected UV's and padding."
bl_options = {'REGISTER', 'UNDO'}
bl_idname = "uv.textools_uv_channel_add"
bl_label = "Add UV Channel"
bl_description = "Add a new UV channel with smart UV projected UV's and padding."
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
if bpy.context.active_object == None:
return False
if bpy.context.active_object.type != 'MESH':
return False
return True
@classmethod
def poll(cls, context):
if bpy.context.active_object == None:
return False
if bpy.context.active_object.type != 'MESH':
return False
return True
@classmethod
def poll(cls, context):
if bpy.context.active_object == None:
return False
if bpy.context.active_object.type != 'MESH':
return False
if len(bpy.context.selected_objects) != 1:
return False
@classmethod
def poll(cls, context):
if bpy.context.active_object == None:
return False
if bpy.context.active_object.type != 'MESH':
return False
if len(bpy.context.selected_objects) != 1:
return False
return True
return True
def execute(self, context):
print("Add UV")
if len( bpy.context.object.data.uv_layers ) == 0:
# Create first UV channel
if bpy.context.active_object.mode != 'EDIT':
bpy.ops.object.mode_set(mode='EDIT')
def execute(self, context):
print("Add UV")
if len( bpy.context.object.data.uv_layers ) == 0:
# Create first UV channel
if bpy.context.active_object.mode != 'EDIT':
bpy.ops.object.mode_set(mode='EDIT')
# Smart project UV's
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.uv.smart_project(
angle_limit=65,
island_margin=0.5,
user_area_weight=0,
use_aspect=True,
stretch_to_bounds=True
)
# Smart project UV's
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.uv.smart_project(
angle_limit=65,
island_margin=0.5,
user_area_weight=0,
use_aspect=True,
stretch_to_bounds=True
)
# Re-Apply padding as normalized values
bpy.ops.uv.select_all(action='SELECT')
bpy.ops.uv.pack_islands(margin=utilities_ui.get_padding())
else:
# Add new UV channel based on last
bpy.ops.mesh.uv_texture_add()
# Re-Apply padding as normalized values
bpy.ops.uv.select_all(action='SELECT')
bpy.ops.uv.pack_islands(margin=utilities_ui.get_padding())
else:
# Add new UV channel based on last
bpy.ops.mesh.uv_texture_add()
# Get current index
index = len(bpy.context.object.data.uv_layers)-1
bpy.context.object.data.uv_layers.active_index = index
bpy.context.scene.texToolsSettings.uv_channel = str(index)
return {'FINISHED'}
# Get current index
index = len(bpy.context.object.data.uv_layers)-1
bpy.context.object.data.uv_layers.active_index = index
bpy.context.scene.texToolsSettings.uv_channel = str(index)
return {'FINISHED'}
bpy.utils.register_class(op)

@ -7,64 +7,64 @@ from math import pi
class op(bpy.types.Operator):
bl_idname = "uv.textools_uv_channel_swap"
bl_label = "Move UV Channel"
bl_description = "Move UV channel up or down"
bl_options = {'REGISTER', 'UNDO'}
bl_idname = "uv.textools_uv_channel_swap"
bl_label = "Move UV Channel"
bl_description = "Move UV channel up or down"
bl_options = {'REGISTER', 'UNDO'}
is_down : bpy.props.BoolProperty(default=False)
is_down : bpy.props.BoolProperty(default=False)
@classmethod
def poll(cls, context):
if bpy.context.active_object == None:
return False
if bpy.context.active_object.type != 'MESH':
return False
if len(bpy.context.object.data.uv_layers) <= 1:
return False
return True
@classmethod
def poll(cls, context):
if bpy.context.active_object == None:
return False
if bpy.context.active_object.type != 'MESH':
return False
if len(bpy.context.object.data.uv_layers) <= 1:
return False
return True
def execute(self, context):
uv_layers = bpy.context.object.data.uv_layers
def execute(self, context):
uv_layers = bpy.context.object.data.uv_layers
if uv_layers.active_index == 0 and not self.is_down:
return {'FINISHED'}
elif uv_layers.active_index == len(uv_layers)-1 and self.is_down:
return {'FINISHED'}
if uv_layers.active_index == 0 and not self.is_down:
return {'FINISHED'}
elif uv_layers.active_index == len(uv_layers)-1 and self.is_down:
return {'FINISHED'}
def get_index(name):
return ([i for i in range(len(uv_layers)) if uv_layers[i].name == name])[0]
def get_index(name):
return ([i for i in range(len(uv_layers)) if uv_layers[i].name == name])[0]
def move_bottom(name):
# Set index
uv_layers.active_index = get_index(name)
# Copy (to bottom)
bpy.ops.mesh.uv_texture_add()
# Delete previous
uv_layers.active_index = get_index(name)
bpy.ops.mesh.uv_texture_remove()
# Rename new
uv_layers.active_index = len(uv_layers)-1
uv_layers.active.name = name
def move_bottom(name):
# Set index
uv_layers.active_index = get_index(name)
# Copy (to bottom)
bpy.ops.mesh.uv_texture_add()
# Delete previous
uv_layers.active_index = get_index(name)
bpy.ops.mesh.uv_texture_remove()
# Rename new
uv_layers.active_index = len(uv_layers)-1
uv_layers.active.name = name
count = len(uv_layers)
count = len(uv_layers)
index_A = uv_layers.active_index
index_B = index_A + (1 if self.is_down else -1)
index_A = uv_layers.active_index
index_B = index_A + (1 if self.is_down else -1)
if not self.is_down:
# Move up
for n in [uv_layers[i].name for i in range(index_B, count) if i != index_A]:
move_bottom(n)
bpy.context.scene.texToolsSettings.uv_channel = str(index_B)
if not self.is_down:
# Move up
for n in [uv_layers[i].name for i in range(index_B, count) if i != index_A]:
move_bottom(n)
bpy.context.scene.texToolsSettings.uv_channel = str(index_B)
elif self.is_down:
# Move down
for n in [uv_layers[i].name for i in range(index_A, count) if i != index_B]:
move_bottom(n)
bpy.context.scene.texToolsSettings.uv_channel = str(index_B)
elif self.is_down:
# Move down
for n in [uv_layers[i].name for i in range(index_A, count) if i != index_B]:
move_bottom(n)
bpy.context.scene.texToolsSettings.uv_channel = str(index_B)
return {'FINISHED'}
return {'FINISHED'}
bpy.utils.register_class(op)

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

@ -11,107 +11,107 @@ from . import utilities_uv
from . import utilities_ui
class op(bpy.types.Operator):
bl_idname = "uv.textools_uv_fill"
bl_label = "Fill"
bl_description = "Fill UV selection to UV canvas"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
if not bpy.context.active_object:
return False
if bpy.context.active_object.type != 'MESH':
return False
#Only in Edit mode
if bpy.context.active_object.mode != 'EDIT':
return False
#Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR':
return False
#Requires UV map
if not bpy.context.object.data.uv_layers:
return False
return True
def execute(self, context):
fill(self, context)
return {'FINISHED'}
bl_idname = "uv.textools_uv_fill"
bl_label = "Fill"
bl_description = "Fill UV selection to UV canvas"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
if not bpy.context.active_object:
return False
if bpy.context.active_object.type != 'MESH':
return False
#Only in Edit mode
if bpy.context.active_object.mode != 'EDIT':
return False
#Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR':
return False
#Requires UV map
if not bpy.context.object.data.uv_layers:
return False
return True
def execute(self, context):
fill(self, context)
return {'FINISHED'}
def fill(self, context):
#Store selection
utilities_uv.selection_store()
#Store selection
utilities_uv.selection_store()
bm = bmesh.from_edit_mesh(bpy.context.active_object.data);
uv_layers = bm.loops.layers.uv.verify();
bm = bmesh.from_edit_mesh(bpy.context.active_object.data);
uv_layers = bm.loops.layers.uv.verify();
# 1.) Rotate minimal bounds (less than 45 degrees rotation)
steps = 8
angle = 45; # Starting Angle, half each step
bboxPrevious = utilities_uv.getSelectionBBox()
for i in range(0, steps):
# Rotate right
bpy.ops.transform.rotate(value=(angle * math.pi / 180), orient_axis='Z')
bbox = utilities_uv.getSelectionBBox()
# 1.) Rotate minimal bounds (less than 45 degrees rotation)
steps = 8
angle = 45; # Starting Angle, half each step
bboxPrevious = utilities_uv.getSelectionBBox()
for i in range(0, steps):
# Rotate right
bpy.ops.transform.rotate(value=(angle * math.pi / 180), orient_axis='Z')
bbox = utilities_uv.getSelectionBBox()
print("Rotate {}, diff le: {}".format(angle, bbox['height'] - bboxPrevious['height']))
print("Rotate {}, diff le: {}".format(angle, bbox['height'] - bboxPrevious['height']))
if i == 0:
# Check if already squared
sizeA = bboxPrevious['width'] * bboxPrevious['height']
sizeB = bbox['width'] * bbox['height']
if abs(bbox['width'] - bbox['height']) <= 0.0001 and sizeA < sizeB:
bpy.ops.transform.rotate(value=(-angle * math.pi / 180), orient_axis='Z')
break;
if i == 0:
# Check if already squared
sizeA = bboxPrevious['width'] * bboxPrevious['height']
sizeB = bbox['width'] * bbox['height']
if abs(bbox['width'] - bbox['height']) <= 0.0001 and sizeA < sizeB:
bpy.ops.transform.rotate(value=(-angle * math.pi / 180), orient_axis='Z')
break;
if bbox['minLength'] < bboxPrevious['minLength']:
bboxPrevious = bbox; # Success
else:
# Rotate Left
bpy.ops.transform.rotate(value=(-angle*2 * math.pi / 180), orient_axis='Z')
bbox = utilities_uv.getSelectionBBox()
if bbox['minLength'] < bboxPrevious['minLength']:
bboxPrevious = bbox; # Success
else:
# Restore angle of this iteration
bpy.ops.transform.rotate(value=(angle * math.pi / 180), orient_axis='Z')
if bbox['minLength'] < bboxPrevious['minLength']:
bboxPrevious = bbox; # Success
else:
# Rotate Left
bpy.ops.transform.rotate(value=(-angle*2 * math.pi / 180), orient_axis='Z')
bbox = utilities_uv.getSelectionBBox()
if bbox['minLength'] < bboxPrevious['minLength']:
bboxPrevious = bbox; # Success
else:
# Restore angle of this iteration
bpy.ops.transform.rotate(value=(angle * math.pi / 180), orient_axis='Z')
angle = angle / 2
angle = angle / 2
if bboxPrevious['width'] < bboxPrevious['height']:
bpy.ops.transform.rotate(value=(90 * math.pi / 180), orient_axis='Z')
if bboxPrevious['width'] < bboxPrevious['height']:
bpy.ops.transform.rotate(value=(90 * math.pi / 180), orient_axis='Z')
# 2.) Match width and height to UV bounds
bbox = utilities_uv.getSelectionBBox()
# 2.) Match width and height to UV bounds
bbox = utilities_uv.getSelectionBBox()
scale_x = 1.0 / bbox['width']
scale_y = 1.0 / bbox['height']
print("Scale {} | {}".format(scale_x, scale_y))
scale_x = 1.0 / bbox['width']
scale_y = 1.0 / bbox['height']
print("Scale {} | {}".format(scale_x, scale_y))
bpy.context.tool_settings.transform_pivot_point = 'BOUNDING_BOX_CENTER'
bpy.ops.transform.resize(value=(scale_x, scale_y, 1), constraint_axis=(False, False, False), orient_type='GLOBAL', use_proportional_edit=False)
bpy.context.tool_settings.transform_pivot_point = 'BOUNDING_BOX_CENTER'
bpy.ops.transform.resize(value=(scale_x, scale_y, 1), constraint_axis=(False, False, False), orient_type='GLOBAL', use_proportional_edit=False)
bbox = utilities_uv.getSelectionBBox()
offset_x = -bbox['min'].x
offset_y = -bbox['min'].y
bpy.ops.transform.translate(value=(offset_x, offset_y, 0), constraint_axis=(False, False, False), orient_type='GLOBAL', use_proportional_edit=False)
bbox = utilities_uv.getSelectionBBox()
offset_x = -bbox['min'].x
offset_y = -bbox['min'].y
bpy.ops.transform.translate(value=(offset_x, offset_y, 0), constraint_axis=(False, False, False), orient_type='GLOBAL', use_proportional_edit=False)
#Restore selection
utilities_uv.selection_restore()
#Restore selection
utilities_uv.selection_restore()
bpy.utils.register_class(op)

@ -20,244 +20,244 @@ utilities_ui.icon_register("op_extend_canvas_BR_active.png")
def on_dropdown_size_x(self, context):
self.size_x = int(self.dropdown_size_x)
# context.area.tag_redraw()
self.size_x = int(self.dropdown_size_x)
# context.area.tag_redraw()
def on_dropdown_size_y(self, context):
self.size_y = int(self.dropdown_size_y)
# context.area.tag_redraw()
self.size_y = int(self.dropdown_size_y)
# context.area.tag_redraw()
class op(bpy.types.Operator):
bl_idname = "uv.textools_uv_resize"
bl_label = "Resize Area"
bl_description = "Resize or extend the UV area"
bl_options = {'REGISTER', 'UNDO'}
size_x : bpy.props.IntProperty(
name = "Width",
description="padding size in pixels",
default = 1024,
min = 1,
max = 8192
)
size_y : bpy.props.IntProperty(
name = "Height",
description="padding size in pixels",
default = 1024,
min = 1,
max = 8192
)
dropdown_size_x : bpy.props.EnumProperty(
items = utilities_ui.size_textures,
name = "",
update = on_dropdown_size_x,
default = '1024'
)
dropdown_size_y : bpy.props.EnumProperty(
items = utilities_ui.size_textures,
name = "",
update = on_dropdown_size_y,
default = '1024'
)
direction : bpy.props.EnumProperty(name='direction', items=(
('TL',' ','Top Left', utilities_ui.icon_get("op_extend_canvas_TL_active"),0),
('BL',' ','Bottom Left', utilities_ui.icon_get("op_extend_canvas_BL_active"),2),
('TR',' ','Top Right', utilities_ui.icon_get("op_extend_canvas_TR_active"),1),
('BR',' ','Bottom Right', utilities_ui.icon_get("op_extend_canvas_BR_active"),3)
))
@classmethod
def poll(cls, context):
if not bpy.context.active_object:
return False
if bpy.context.active_object.type != 'MESH':
return False
#Only in Edit mode
if bpy.context.active_object.mode != 'EDIT':
return False
#Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR':
return False
#Requires UV map
if not bpy.context.object.data.uv_layers:
return False
return True
def invoke(self, context, event):
print("Invoke resize area")
self.size_x = bpy.context.scene.texToolsSettings.size[0]
self.size_y = bpy.context.scene.texToolsSettings.size[1]
for item in utilities_ui.size_textures:
if int(item[0]) == self.size_x:
self.dropdown_size_x = item[0]
break
for item in utilities_ui.size_textures:
if int(item[0]) == self.size_y:
self.dropdown_size_y = item[0]
break
return context.window_manager.invoke_props_dialog(self, width = 140)
def check(self, context):
return True
def draw(self, context):
# https://b3d.interplanety.org/en/creating-pop-up-panels-with-user-ui-in-blender-add-on/
layout = self.layout
layout.separator()
# New Size
row = layout.row()
split = row.split(factor=0.6)
c = split.column(align=True)
c.prop(self, "size_x", text="X",expand=True)
c.prop(self, "size_y", text="Y",expand=True)
c = split.column(align=True)
c.prop(self, "dropdown_size_x", text="")
c.prop(self, "dropdown_size_y", text="")
# Direction
col = layout.column(align=True)
col.label(text="Direction")
row = col.row(align=True)
row.prop(self,'direction', expand=True)
# Summary
size_A = "{} x {}".format(bpy.context.scene.texToolsSettings.size[0], bpy.context.scene.texToolsSettings.size[1])
if bpy.context.scene.texToolsSettings.size[0] == bpy.context.scene.texToolsSettings.size[1]:
size_A = "{}²".format(bpy.context.scene.texToolsSettings.size[0])
size_B = "{} x {}".format(self.size_x, self.size_y)
if self.size_x == self.size_y:
size_B = "{}²".format(self.size_x)
layout.label(text="{} to {}".format(
size_A, size_B
))
layout.separator()
def execute(self, context):
#Store selection
utilities_uv.selection_store()
# Get start and end size
size_A = Vector([
bpy.context.scene.texToolsSettings.size[0],
bpy.context.scene.texToolsSettings.size[1]
])
size_B = Vector([
self.size_x,
self.size_y
])
resize_uv(
self,
context,
self.direction,
size_A,
size_B
)
resize_image(
context,
self.direction,
size_A,
size_B
)
bl_idname = "uv.textools_uv_resize"
bl_label = "Resize Area"
bl_description = "Resize or extend the UV area"
bl_options = {'REGISTER', 'UNDO'}
size_x : bpy.props.IntProperty(
name = "Width",
description="padding size in pixels",
default = 1024,
min = 1,
max = 8192
)
size_y : bpy.props.IntProperty(
name = "Height",
description="padding size in pixels",
default = 1024,
min = 1,
max = 8192
)
dropdown_size_x : bpy.props.EnumProperty(
items = utilities_ui.size_textures,
name = "",
update = on_dropdown_size_x,
default = '1024'
)
dropdown_size_y : bpy.props.EnumProperty(
items = utilities_ui.size_textures,
name = "",
update = on_dropdown_size_y,
default = '1024'
)
direction : bpy.props.EnumProperty(name='direction', items=(
('TL',' ','Top Left', utilities_ui.icon_get("op_extend_canvas_TL_active"),0),
('BL',' ','Bottom Left', utilities_ui.icon_get("op_extend_canvas_BL_active"),2),
('TR',' ','Top Right', utilities_ui.icon_get("op_extend_canvas_TR_active"),1),
('BR',' ','Bottom Right', utilities_ui.icon_get("op_extend_canvas_BR_active"),3)
))
@classmethod
def poll(cls, context):
if not bpy.context.active_object:
return False
if bpy.context.active_object.type != 'MESH':
return False
#Only in Edit mode
if bpy.context.active_object.mode != 'EDIT':
return False
#Only in UV editor mode
if bpy.context.area.type != 'IMAGE_EDITOR':
return False
#Requires UV map
if not bpy.context.object.data.uv_layers:
return False
return True
def invoke(self, context, event):
print("Invoke resize area")
self.size_x = bpy.context.scene.texToolsSettings.size[0]
self.size_y = bpy.context.scene.texToolsSettings.size[1]
for item in utilities_ui.size_textures:
if int(item[0]) == self.size_x:
self.dropdown_size_x = item[0]
break
for item in utilities_ui.size_textures:
if int(item[0]) == self.size_y:
self.dropdown_size_y = item[0]
break
return context.window_manager.invoke_props_dialog(self, width = 140)
def check(self, context):
return True
def draw(self, context):
# https://b3d.interplanety.org/en/creating-pop-up-panels-with-user-ui-in-blender-add-on/
layout = self.layout
layout.separator()
# New Size
row = layout.row()
split = row.split(factor=0.6)
c = split.column(align=True)
c.prop(self, "size_x", text="X",expand=True)
c.prop(self, "size_y", text="Y",expand=True)
c = split.column(align=True)
c.prop(self, "dropdown_size_x", text="")
c.prop(self, "dropdown_size_y", text="")
# Direction
col = layout.column(align=True)
col.label(text="Direction")
row = col.row(align=True)
row.prop(self,'direction', expand=True)
# Summary
size_A = "{} x {}".format(bpy.context.scene.texToolsSettings.size[0], bpy.context.scene.texToolsSettings.size[1])
if bpy.context.scene.texToolsSettings.size[0] == bpy.context.scene.texToolsSettings.size[1]:
size_A = "{}²".format(bpy.context.scene.texToolsSettings.size[0])
size_B = "{} x {}".format(self.size_x, self.size_y)
if self.size_x == self.size_y:
size_B = "{}²".format(self.size_x)
layout.label(text="{} to {}".format(
size_A, size_B
))
layout.separator()
def execute(self, context):
#Store selection
utilities_uv.selection_store()
# Get start and end size
size_A = Vector([
bpy.context.scene.texToolsSettings.size[0],
bpy.context.scene.texToolsSettings.size[1]
])
size_B = Vector([
self.size_x,
self.size_y
])
resize_uv(
self,
context,
self.direction,
size_A,
size_B
)
resize_image(
context,
self.direction,
size_A,
size_B
)
bpy.context.scene.texToolsSettings.size[0] = self.size_x
bpy.context.scene.texToolsSettings.size[1] = self.size_y
bpy.context.scene.texToolsSettings.size[0] = self.size_x
bpy.context.scene.texToolsSettings.size[1] = self.size_y
#Restore selection
utilities_uv.selection_restore()
#Restore selection
utilities_uv.selection_restore()
return {'FINISHED'}
return {'FINISHED'}
def resize_uv(self, context, mode, size_A, size_B):
# Set pivot
bpy.context.tool_settings.transform_pivot_point = 'CURSOR'
if mode == 'TL':
bpy.ops.uv.cursor_set(location=Vector([0,1]))
elif mode == 'TR':
bpy.ops.uv.cursor_set(location=Vector([1,1]))
elif mode == 'BL':
bpy.ops.uv.cursor_set(location=Vector([0,0]))
elif mode == 'BR':
bpy.ops.uv.cursor_set(location=Vector([1,0]))
# Set pivot
bpy.context.tool_settings.transform_pivot_point = 'CURSOR'
if mode == 'TL':
bpy.ops.uv.cursor_set(location=Vector([0,1]))
elif mode == 'TR':
bpy.ops.uv.cursor_set(location=Vector([1,1]))
elif mode == 'BL':
bpy.ops.uv.cursor_set(location=Vector([0,0]))
elif mode == 'BR':
bpy.ops.uv.cursor_set(location=Vector([1,0]))
# Select all UV faces
bpy.ops.uv.select_all(action='SELECT')
# Select all UV faces
bpy.ops.uv.select_all(action='SELECT')
# Resize
scale_x = size_A.x / size_B.x
scale_y = size_A.y / size_B.y
bpy.ops.transform.resize(value=(scale_x, scale_y, 1.0), use_proportional_edit=False)
# Resize
scale_x = size_A.x / size_B.x
scale_y = size_A.y / size_B.y
bpy.ops.transform.resize(value=(scale_x, scale_y, 1.0), use_proportional_edit=False)
def resize_image(context, mode, size_A, size_B):
print("resize image {}".format( context.area.spaces ))
# Notes: https://blender.stackexchange.com/questions/31514/active-image-of-uv-image-editor
# https://docs.blender.org/api/blender_python_api_2_70_4/bpy.types.SpaceImageEditor.html
if context.area.spaces.active != None:
if context.area.spaces.active.image != None:
image = context.area.spaces.active.image
image_obj = utilities_texel.get_object_texture_image(bpy.context.active_object)
if name_texture in image.name or image == image_obj:
# Resize Image UV editor background image
utilities_texel.image_resize(image, int(size_B.x), int(size_B.y))
else:
# No Image assigned
# Get background color from theme + 1.25x brighter
theme = bpy.context.preferences.themes[0]
color = theme.image_editor.space.back.copy()
color.r*= 1.15
color.g*= 1.15
color.b*= 1.15
image = None
if name_texture in bpy.data.images:
# TexTools Image already exists
image = bpy.data.images[name_texture]
image.scale( int(size_B.x), int(size_B.y) )
image.generated_width = int(size_B.x)
image.generated_height = int(size_B.y)
else:
# Create new image
image = bpy.data.images.new(name_texture, width=int(size_B.x), height=int(size_B.y))
image.generated_color = (color.r, color.g, color.b, 1.0)
image.generated_type = 'BLANK'
image.generated_width = int(size_B.x)
image.generated_height = int(size_B.y)
# Assign in UV view
context.area.spaces.active.image = image
# Clean up images and materials
utilities_texel.checker_images_cleanup()
print("resize image {}".format( context.area.spaces ))
# Notes: https://blender.stackexchange.com/questions/31514/active-image-of-uv-image-editor
# https://docs.blender.org/api/blender_python_api_2_70_4/bpy.types.SpaceImageEditor.html
if context.area.spaces.active != None:
if context.area.spaces.active.image != None:
image = context.area.spaces.active.image
image_obj = utilities_texel.get_object_texture_image(bpy.context.active_object)
if name_texture in image.name or image == image_obj:
# Resize Image UV editor background image
utilities_texel.image_resize(image, int(size_B.x), int(size_B.y))
else:
# No Image assigned
# Get background color from theme + 1.25x brighter
theme = bpy.context.preferences.themes[0]
color = theme.image_editor.space.back.copy()
color.r*= 1.15
color.g*= 1.15
color.b*= 1.15
image = None
if name_texture in bpy.data.images:
# TexTools Image already exists
image = bpy.data.images[name_texture]
image.scale( int(size_B.x), int(size_B.y) )
image.generated_width = int(size_B.x)
image.generated_height = int(size_B.y)
else:
# Create new image
image = bpy.data.images.new(name_texture, width=int(size_B.x), height=int(size_B.y))
image.generated_color = (color.r, color.g, color.b, 1.0)
image.generated_type = 'BLANK'
image.generated_width = int(size_B.x)
image.generated_height = int(size_B.y)
# Assign in UV view
context.area.spaces.active.image = image
# Clean up images and materials
utilities_texel.checker_images_cleanup()

@ -8,37 +8,37 @@ from math import pi
from . import utilities_texel
class op(bpy.types.Operator):
bl_idname = "uv.textools_uv_size_get"
bl_label = "Get Size"
bl_description = "Get selected object's texture size"
bl_idname = "uv.textools_uv_size_get"
bl_label = "Get Size"
bl_description = "Get selected object's texture size"
@classmethod
def poll(cls, context):
if not bpy.context.active_object:
return False
if bpy.context.active_object not in bpy.context.selected_objects:
return False
@classmethod
def poll(cls, context):
if not bpy.context.active_object:
return False
if bpy.context.active_object not in bpy.context.selected_objects:
return False
if bpy.context.active_object.type != 'MESH':
return False
if bpy.context.active_object.type != 'MESH':
return False
return True
def execute(self, context):
get_size(self, context)
return {'FINISHED'}
return True
def execute(self, context):
get_size(self, context)
return {'FINISHED'}
def get_size(self, context):
image = utilities_texel.get_object_texture_image (bpy.context.active_object)
image = utilities_texel.get_object_texture_image (bpy.context.active_object)
if not image:
self.report({'ERROR_INVALID_INPUT'}, "No Texture found on selected object" )
return
if not image:
self.report({'ERROR_INVALID_INPUT'}, "No Texture found on selected object" )
return
bpy.context.scene.texToolsSettings.size[0] = image.size[0]
bpy.context.scene.texToolsSettings.size[1] = image.size[1]
bpy.context.scene.texToolsSettings.size[0] = image.size[0]
bpy.context.scene.texToolsSettings.size[1] = image.size[1]
bpy.utils.register_class(op)

File diff suppressed because it is too large Load Diff

@ -15,213 +15,213 @@ gamma = 2.2
def assign_slot(obj, index):
if index < len(obj.material_slots):
obj.material_slots[index].material = get_material(index)
if index < len(obj.material_slots):
obj.material_slots[index].material = get_material(index)
# Verify color
assign_color(index)
# Verify color
assign_color(index)
def safe_color(color):
if len(color) == 3:
if bpy.app.version > (2, 80, 0):
# Newer blender versions use RGBA
return (color[0], color[1], color[2], 1)
else:
return color
elif len(color) == 4:
if bpy.app.version > (2, 80, 0):
# Newer blender versions use RGBA
return color
else:
return (color[0], color[1], color[2])
return color
if len(color) == 3:
if bpy.app.version > (2, 80, 0):
# Newer blender versions use RGBA
return (color[0], color[1], color[2], 1)
else:
return color
elif len(color) == 4:
if bpy.app.version > (2, 80, 0):
# Newer blender versions use RGBA
return color
else:
return (color[0], color[1], color[2])
return color
def assign_color(index):
material = get_material(index)
if material:
# material.use_nodes = False
rgb = get_color(index)
rgba = (rgb[0], rgb[1], rgb[2], 1)
material = get_material(index)
if material:
# material.use_nodes = False
rgb = get_color(index)
rgba = (rgb[0], rgb[1], rgb[2], 1)
if material.use_nodes and bpy.context.scene.render.engine == 'CYCLES' or material.use_nodes and bpy.context.scene.render.engine == 'BLENDER_EEVEE' :
# Cycles material (Preferred for baking)
material.node_tree.nodes["Principled BSDF"].inputs[0].default_value = rgba
material.diffuse_color = rgba
if material.use_nodes and bpy.context.scene.render.engine == 'CYCLES' or material.use_nodes and bpy.context.scene.render.engine == 'BLENDER_EEVEE' :
# Cycles material (Preferred for baking)
material.node_tree.nodes["Principled BSDF"].inputs[0].default_value = rgba
material.diffuse_color = rgba
elif not material.use_nodes and bpy.context.scene.render.engine == 'BLENDER_EEVEE':
# Legacy render engine, not suited for baking
material.diffuse_color = rgba
elif not material.use_nodes and bpy.context.scene.render.engine == 'BLENDER_EEVEE':
# Legacy render engine, not suited for baking
material.diffuse_color = rgba
def get_material(index):
name = get_name(index)
name = get_name(index)
# Material already exists?
if name in bpy.data.materials:
material = bpy.data.materials[name];
# Material already exists?
if name in bpy.data.materials:
material = bpy.data.materials[name];
# Check for incorrect matreials for current render engine
if not material:
replace_material(index)
# Check for incorrect matreials for current render engine
if not material:
replace_material(index)
if not material.use_nodes and bpy.context.scene.render.engine == 'CYCLES':
replace_material(index)
if not material.use_nodes and bpy.context.scene.render.engine == 'CYCLES':
replace_material(index)
elif material.use_nodes and bpy.context.scene.render.engine == 'BLENDER_EEVEE':
replace_material(index)
elif material.use_nodes and bpy.context.scene.render.engine == 'BLENDER_EEVEE':
replace_material(index)
else:
return material;
else:
return material;
print("Could nt find {} , create a new one??".format(name))
print("Could nt find {} , create a new one??".format(name))
material = create_material(index)
assign_color(index)
return material
material = create_material(index)
assign_color(index)
return material
# Replaace an existing material with a new one
# This is sometimes necessary after switching the render engine
def replace_material(index):
name = get_name(index)
name = get_name(index)
print("Replace material and create new")
print("Replace material and create new")
# Check if material exists
if name in bpy.data.materials:
material = bpy.data.materials[name];
# Check if material exists
if name in bpy.data.materials:
material = bpy.data.materials[name];
# Collect material slots we have to re-assign
slots = []
for obj in bpy.context.view_layer.objects:
for slot in obj.material_slots:
if slot.material == material:
slots.append(slot)
# Collect material slots we have to re-assign
slots = []
for obj in bpy.context.view_layer.objects:
for slot in obj.material_slots:
if slot.material == material:
slots.append(slot)
# Get new material
material.user_clear()
bpy.data.materials.remove(material)
# Re-assign new material to all previous slots
material = create_material(index)
for slot in slots:
slot.material = material;
# Get new material
material.user_clear()
bpy.data.materials.remove(material)
# Re-assign new material to all previous slots
material = create_material(index)
for slot in slots:
slot.material = material;
def create_material(index):
name = get_name(index)
name = get_name(index)
# Create new image instead
material = bpy.data.materials.new(name)
material.preview_render_type = 'FLAT'
# Create new image instead
material = bpy.data.materials.new(name)
material.preview_render_type = 'FLAT'
if bpy.context.scene.render.engine == 'CYCLES':
# Cycles: prefer nodes as it simplifies baking
material.use_nodes = True
if bpy.context.scene.render.engine == 'CYCLES':
# Cycles: prefer nodes as it simplifies baking
material.use_nodes = True
return material
return material
def get_name(index):
return (material_prefix+"{:02d}").format(index)
return (material_prefix+"{:02d}").format(index)
def get_color(index):
if index < bpy.context.scene.texToolsSettings.color_ID_count:
return getattr(bpy.context.scene.texToolsSettings, "color_ID_color_{}".format(index))
if index < bpy.context.scene.texToolsSettings.color_ID_count:
return getattr(bpy.context.scene.texToolsSettings, "color_ID_color_{}".format(index))
# Default return (Black)
return (0, 0, 0)
# Default return (Black)
return (0, 0, 0)
def set_color(index, color):
if index < bpy.context.scene.texToolsSettings.color_ID_count:
setattr(bpy.context.scene.texToolsSettings, "color_ID_color_{}".format(index), color)
if index < bpy.context.scene.texToolsSettings.color_ID_count:
setattr(bpy.context.scene.texToolsSettings, "color_ID_color_{}".format(index), color)
def validate_face_colors(obj):
# Validate face colors and material slots
previous_mode = bpy.context.object.mode;
count = bpy.context.scene.texToolsSettings.color_ID_count
# Validate face colors and material slots
previous_mode = bpy.context.object.mode;
count = bpy.context.scene.texToolsSettings.color_ID_count
# Verify enough material slots
if len(obj.material_slots) < count:
for i in range(count):
if len(obj.material_slots) < count:
bpy.ops.object.material_slot_add()
assign_slot(obj, len(obj.material_slots)-1)
else:
break
# Verify enough material slots
if len(obj.material_slots) < count:
for i in range(count):
if len(obj.material_slots) < count:
bpy.ops.object.material_slot_add()
assign_slot(obj, len(obj.material_slots)-1)
else:
break
# TODO: Check face.material_index
bpy.ops.object.mode_set(mode='EDIT')
bm = bmesh.from_edit_mesh(obj.data);
for face in bm.faces:
face.material_index%= count
obj.data.update()
# TODO: Check face.material_index
bpy.ops.object.mode_set(mode='EDIT')
bm = bmesh.from_edit_mesh(obj.data);
for face in bm.faces:
face.material_index%= count
obj.data.update()
# Remove material slots that are not used
if len(obj.material_slots) > count:
bpy.ops.object.mode_set(mode='OBJECT')
for i in range(len(obj.material_slots) - count):
if len(obj.material_slots) > count:
# Remove last
bpy.context.object.active_material_index = len(obj.material_slots)-1
bpy.ops.object.material_slot_remove()
# Remove material slots that are not used
if len(obj.material_slots) > count:
bpy.ops.object.mode_set(mode='OBJECT')
for i in range(len(obj.material_slots) - count):
if len(obj.material_slots) > count:
# Remove last
bpy.context.object.active_material_index = len(obj.material_slots)-1
bpy.ops.object.material_slot_remove()
# Restore previous mode
bpy.ops.object.mode_set(mode=previous_mode)
# Restore previous mode
bpy.ops.object.mode_set(mode=previous_mode)
def hex_to_color(hex):
hex = hex.strip('#')
lv = len(hex)
fin = list(int(hex[i:i + lv // 3], 16) for i in range(0, lv, lv // 3))
r = pow(fin[0] / 255, gamma)
g = pow(fin[1] / 255, gamma)
b = pow(fin[2] / 255, gamma)
fin.clear()
fin.append(r)
fin.append(g)
fin.append(b)
return tuple(fin)
hex = hex.strip('#')
lv = len(hex)
fin = list(int(hex[i:i + lv // 3], 16) for i in range(0, lv, lv // 3))
r = pow(fin[0] / 255, gamma)
g = pow(fin[1] / 255, gamma)
b = pow(fin[2] / 255, gamma)
fin.clear()
fin.append(r)
fin.append(g)
fin.append(b)
return tuple(fin)
def color_to_hex(color):
rgb = []
for i in range(3):
rgb.append( pow(color[i] , 1.0/gamma) )
rgb = []
for i in range(3):
rgb.append( pow(color[i] , 1.0/gamma) )
r = int(rgb[0]*255)
g = int(rgb[1]*255)
b = int(rgb[2]*255)
r = int(rgb[0]*255)
g = int(rgb[1]*255)
b = int(rgb[2]*255)
return "#{:02X}{:02X}{:02X}".format(r,g,b)
return "#{:02X}{:02X}{:02X}".format(r,g,b)
def get_color_id(index, count):
# Get unique color
color = Color()
color.hsv = ( index / (count) ), 0.9, 1.0
return color
# Get unique color
color = Color()
color.hsv = ( index / (count) ), 0.9, 1.0
return color

@ -7,114 +7,114 @@ from mathutils import Vector
# Find a mesh that contains UV mesh shape keys
def find_uv_mesh(objects, insideModifiers=True):
for obj in objects:
# Requires mesh & UV channel
if obj and obj.type == 'MESH':
for obj in objects:
# Requires mesh & UV channel
if obj and obj.type == 'MESH':
# Contains shape keys?
if obj.data.shape_keys and len(obj.data.shape_keys.key_blocks) == 2:
if "uv" in obj.data.shape_keys.key_blocks and "model" in obj.data.shape_keys.key_blocks:
return obj
# Contains shape keys?
if obj.data.shape_keys and len(obj.data.shape_keys.key_blocks) == 2:
if "uv" in obj.data.shape_keys.key_blocks and "model" in obj.data.shape_keys.key_blocks:
return obj
if insideModifiers:
# Find via mesh deform modifier
if len(obj.modifiers) > 0:
for modifier in obj.modifiers:
if modifier.type == 'SURFACE_DEFORM':
if modifier.target:
if modifier.target.data.shape_keys and len(modifier.target.data.shape_keys.key_blocks) == 2:
return modifier.target
return None
if insideModifiers:
# Find via mesh deform modifier
if len(obj.modifiers) > 0:
for modifier in obj.modifiers:
if modifier.type == 'SURFACE_DEFORM':
if modifier.target:
if modifier.target.data.shape_keys and len(modifier.target.data.shape_keys.key_blocks) == 2:
return modifier.target
return None
# # Find meshes that can be wrapped aka texture meshes
def find_texture_meshes(objects):
obj_textures = []
obj_textures = []
for obj in objects:
if obj and obj.type == 'MESH':
if find_uv_mesh([obj], insideModifiers=False) == None:
if obj not in obj_textures:
obj_textures.append(obj)
for obj in objects:
if obj and obj.type == 'MESH':
if find_uv_mesh([obj], insideModifiers=False) == None:
if obj not in obj_textures:
obj_textures.append(obj)
return obj_textures
return obj_textures
def uv_mesh_clear(obj_uv):
# Remove Previous Modifiers
if "Solidify" in obj_uv.modifiers:
obj_uv.modifiers.remove( obj_uv.modifiers["Solidify"] )
# Remove Solidify and push modifiers
# Remove Previous Modifiers
if "Solidify" in obj_uv.modifiers:
obj_uv.modifiers.remove( obj_uv.modifiers["Solidify"] )
# Remove Solidify and push modifiers
def uv_mesh_fit(obj_uv, obj_textures):
# Clear first shape transition
bpy.context.scene.texToolsSettings.meshtexture_wrap = 0
# Clear modifiers first
uv_mesh_clear(obj_uv)
# Add solidify modifier
modifier_solid = obj_uv.modifiers.new(name="Solidify", type='SOLIDIFY')
modifier_solid.offset = 1
modifier_solid.thickness = 0 #scale*0.1 #10% height
modifier_solid.use_even_offset = True
modifier_solid.thickness_clamp = 0
modifier_solid.use_quality_normals = True
min_z = obj_uv.location.z
max_z = obj_uv.location.z
for i in range(len(obj_textures)):
obj = obj_textures[i]
# Min Max Z
if i == 0:
min_z = get_bbox(obj)['min'].z
max_z = get_bbox(obj)['max'].z
else:
min_z = min(min_z, get_bbox(obj)['min'].z)
max_z = max(max_z, get_bbox(obj)['max'].z)
# Set thickness
size = max(0.1, (max_z - min_z))
min_z-= size*0.25 #Padding
max_z+= size*0.25
size = (max_z - min_z)
modifier_solid.thickness = size
# Set offset
if size > 0:
p_z = (obj_uv.location.z - min_z) / (max_z - min_z)
modifier_solid.offset = -(p_z-0.5)/0.5
else:
modifier_solid.offset = 0
# Clear first shape transition
bpy.context.scene.texToolsSettings.meshtexture_wrap = 0
# Clear modifiers first
uv_mesh_clear(obj_uv)
# Add solidify modifier
modifier_solid = obj_uv.modifiers.new(name="Solidify", type='SOLIDIFY')
modifier_solid.offset = 1
modifier_solid.thickness = 0 #scale*0.1 #10% height
modifier_solid.use_even_offset = True
modifier_solid.thickness_clamp = 0
modifier_solid.use_quality_normals = True
min_z = obj_uv.location.z
max_z = obj_uv.location.z
for i in range(len(obj_textures)):
obj = obj_textures[i]
# Min Max Z
if i == 0:
min_z = get_bbox(obj)['min'].z
max_z = get_bbox(obj)['max'].z
else:
min_z = min(min_z, get_bbox(obj)['min'].z)
max_z = max(max_z, get_bbox(obj)['max'].z)
# Set thickness
size = max(0.1, (max_z - min_z))
min_z-= size*0.25 #Padding
max_z+= size*0.25
size = (max_z - min_z)
modifier_solid.thickness = size
# Set offset
if size > 0:
p_z = (obj_uv.location.z - min_z) / (max_z - min_z)
modifier_solid.offset = -(p_z-0.5)/0.5
else:
modifier_solid.offset = 0
def get_bbox(obj):
corners = [obj.matrix_world @ Vector(corner) for corner in obj.bound_box]
# Get world space Min / Max
box_min = Vector((corners[0].x, corners[0].y, corners[0].z))
box_max = Vector((corners[0].x, corners[0].y, corners[0].z))
for corner in corners:
# box_min.x = -8
box_min.x = min(box_min.x, corner.x)
box_min.y = min(box_min.y, corner.y)
box_min.z = min(box_min.z, corner.z)
box_max.x = max(box_max.x, corner.x)
box_max.y = max(box_max.y, corner.y)
box_max.z = max(box_max.z, corner.z)
return {
'min':box_min,
'max':box_max,
'size':(box_max-box_min),
'center':box_min+(box_max-box_min)/2
}
corners = [obj.matrix_world @ Vector(corner) for corner in obj.bound_box]
# Get world space Min / Max
box_min = Vector((corners[0].x, corners[0].y, corners[0].z))
box_max = Vector((corners[0].x, corners[0].y, corners[0].z))
for corner in corners:
# box_min.x = -8
box_min.x = min(box_min.x, corner.x)
box_min.y = min(box_min.y, corner.y)
box_min.z = min(box_min.z, corner.z)
box_max.x = max(box_max.x, corner.x)
box_max.y = max(box_max.y, corner.y)
box_max.z = max(box_max.z, corner.z)
return {
'min':box_min,
'max':box_max,
'size':(box_max-box_min),
'center':box_min+(box_max-box_min)/2
}

@ -11,128 +11,128 @@ image_material_prefix = "TT_checker_"
# Return all faces of selected objects or only selected faces
def get_selected_object_faces():
object_faces_indexies = {}
object_faces_indexies = {}
previous_mode = bpy.context.object.mode
previous_mode = bpy.context.object.mode
if bpy.context.object.mode == 'EDIT':
# Only selected Mesh faces
obj = bpy.context.active_object
if obj.type == 'MESH' and obj.data.uv_layers:
bm = bmesh.from_edit_mesh(obj.data)
bm.faces.ensure_lookup_table()
object_faces_indexies[obj] = [face.index for face in bm.faces if face.select]
else:
# Selected objects with all faces each
selected_objects = [obj for obj in bpy.context.selected_objects]
for obj in selected_objects:
if obj.type == 'MESH' and obj.data.uv_layers:
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='DESELECT')
bpy.context.view_layer.objects.active = obj
obj.select_set( state = True, view_layer = None)
if bpy.context.object.mode == 'EDIT':
# Only selected Mesh faces
obj = bpy.context.active_object
if obj.type == 'MESH' and obj.data.uv_layers:
bm = bmesh.from_edit_mesh(obj.data)
bm.faces.ensure_lookup_table()
object_faces_indexies[obj] = [face.index for face in bm.faces if face.select]
else:
# Selected objects with all faces each
selected_objects = [obj for obj in bpy.context.selected_objects]
for obj in selected_objects:
if obj.type == 'MESH' and obj.data.uv_layers:
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='DESELECT')
bpy.context.view_layer.objects.active = obj
obj.select_set( state = True, view_layer = None)
bpy.ops.object.mode_set(mode='EDIT')
bm = bmesh.from_edit_mesh(obj.data)
bm.faces.ensure_lookup_table()
object_faces_indexies[obj] = [face.index for face in bm.faces]
bpy.ops.object.mode_set(mode='EDIT')
bm = bmesh.from_edit_mesh(obj.data)
bm.faces.ensure_lookup_table()
object_faces_indexies[obj] = [face.index for face in bm.faces]
bpy.ops.object.mode_set(mode=previous_mode)
bpy.ops.object.mode_set(mode=previous_mode)
return object_faces_indexies
return object_faces_indexies
def get_object_texture_image(obj):
previous_mode = bpy.context.active_object.mode
bpy.ops.object.mode_set(mode='OBJECT')
previous_mode = bpy.context.active_object.mode
bpy.ops.object.mode_set(mode='OBJECT')
# Search in material & texture slots
for slot_mat in obj.material_slots:
# Search in material & texture slots
for slot_mat in obj.material_slots:
if slot_mat.material:
if slot_mat.material:
# Check for traditional texture slots in material
for slot_tex in slot_mat.material.texture_paint_slots:
if slot_tex and slot_tex.texture and hasattr(slot_tex.texture , 'image'):
return slot_tex.texture.image
# Check for traditional texture slots in material
for slot_tex in slot_mat.material.texture_paint_slots:
if slot_tex and slot_tex.texture and hasattr(slot_tex.texture , 'image'):
return slot_tex.texture.image
# Check if material uses Nodes
if hasattr(slot_mat.material , 'node_tree'):
if slot_mat.material.node_tree:
for node in slot_mat.material.node_tree.nodes:
if type(node) is bpy.types.ShaderNodeTexImage:
if node.image:
return node.image
# Check if material uses Nodes
if hasattr(slot_mat.material , 'node_tree'):
if slot_mat.material.node_tree:
for node in slot_mat.material.node_tree.nodes:
if type(node) is bpy.types.ShaderNodeTexImage:
if node.image:
return node.image
return None
return None
def image_resize(image, size_x, size_y):
if image and image.source == 'FILE' or image.source == 'GENERATED':
image.generated_width = int(size_x)
image.generated_height = int(size_y)
image.scale( int(size_x), int(size_y) )
if image and image.source == 'FILE' or image.source == 'GENERATED':
image.generated_width = int(size_x)
image.generated_height = int(size_y)
image.scale( int(size_x), int(size_y) )
def checker_images_cleanup():
# Clean up unused images
for image in bpy.data.images:
if image and image_material_prefix in image.name:
# Remove unused images
if not image.users:
image.user_clear()
bpy.data.images.remove(image)
return
# Check if name missmatches size
name = get_checker_name(image.generated_type , image.size[0], image.size[1])
if image.name != name:
# In cycles find related material as well
if image.name in bpy.data.materials:
bpy.data.materials[image.name].name = name
image.name = name
for material in bpy.data.materials:
if material and image_material_prefix in material.name:
# Remove unused images
if not material.users:
material.user_clear()
bpy.data.materials.remove(material)
# Clean up unused images
for image in bpy.data.images:
if image and image_material_prefix in image.name:
# Remove unused images
if not image.users:
image.user_clear()
bpy.data.images.remove(image)
return
# Check if name missmatches size
name = get_checker_name(image.generated_type , image.size[0], image.size[1])
if image.name != name:
# In cycles find related material as well
if image.name in bpy.data.materials:
bpy.data.materials[image.name].name = name
image.name = name
for material in bpy.data.materials:
if material and image_material_prefix in material.name:
# Remove unused images
if not material.users:
material.user_clear()
bpy.data.materials.remove(material)
def get_checker_name(mode, size_x, size_y):
return (image_material_prefix+"{1}x{2}_{0}").format(mode, size_x, size_y)
return (image_material_prefix+"{1}x{2}_{0}").format(mode, size_x, size_y)
def get_area_triangle_uv(A,B,C, size_x, size_y):
scale_x = size_x / max(size_x, size_y)
scale_y = size_y / max(size_x, size_y)
A.x/=scale_x
B.x/=scale_x
C.x/=scale_x
A.y/=scale_y
B.y/=scale_y
C.y/=scale_y
scale_x = size_x / max(size_x, size_y)
scale_y = size_y / max(size_x, size_y)
A.x/=scale_x
B.x/=scale_x
C.x/=scale_x
A.y/=scale_y
B.y/=scale_y
C.y/=scale_y
return get_area_triangle(A,B,C)
return get_area_triangle(A,B,C)
def get_area_triangle(A,B,C):
# Heron's formula: http://www.1728.org/triang.htm
# area = square root (s • (s - a) • (s - b) • (s - c))
a = (B-A).length
b = (C-B).length
c = (A-C).length
s = (a+b+c)/2
# Use abs(s-a) for values that otherwise generate negative values e.g. pinched UV verts, otherwise math domain error
return math.sqrt(s * abs(s-a) * abs(s-b) * abs(s-c))
# Heron's formula: http://www.1728.org/triang.htm
# area = square root (s • (s - a) • (s - b) • (s - c))
a = (B-A).length
b = (C-B).length
c = (A-C).length
s = (a+b+c)/2
# Use abs(s-a) for values that otherwise generate negative values e.g. pinched UV verts, otherwise math domain error
return math.sqrt(s * abs(s-a) * abs(s-b) * abs(s-c))

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

@ -10,277 +10,277 @@ from . import settings
from . import utilities_ui
def selection_store():
bm = bmesh.from_edit_mesh(bpy.context.active_object.data);
uv_layers = bm.loops.layers.uv.verify();
bm = bmesh.from_edit_mesh(bpy.context.active_object.data);
uv_layers = bm.loops.layers.uv.verify();
# https://blender.stackexchange.com/questions/5781/how-to-list-all-selected-elements-in-python
# print("selectionStore")
settings.selection_uv_mode = bpy.context.scene.tool_settings.uv_select_mode
settings.selection_uv_pivot = bpy.context.tool_settings.transform_pivot_point
settings.selection_uv_pivot_pos = bpy.context.space_data.cursor_location.copy()
# https://blender.stackexchange.com/questions/5781/how-to-list-all-selected-elements-in-python
# print("selectionStore")
settings.selection_uv_mode = bpy.context.scene.tool_settings.uv_select_mode
settings.selection_uv_pivot = bpy.context.tool_settings.transform_pivot_point
settings.selection_uv_pivot_pos = bpy.context.space_data.cursor_location.copy()
#VERT Selection
settings.selection_mode = tuple(bpy.context.scene.tool_settings.mesh_select_mode)
settings.selection_vert_indexies = []
for vert in bm.verts:
if vert.select:
settings.selection_vert_indexies.append(vert.index)
#VERT Selection
settings.selection_mode = tuple(bpy.context.scene.tool_settings.mesh_select_mode)
settings.selection_vert_indexies = []
for vert in bm.verts:
if vert.select:
settings.selection_vert_indexies.append(vert.index)
settings.selection_face_indexies = []
for face in bm.faces:
if face.select:
settings.selection_face_indexies.append(face.index)
settings.selection_face_indexies = []
for face in bm.faces:
if face.select:
settings.selection_face_indexies.append(face.index)
#Face selections (Loops)
settings.selection_uv_loops = []
for face in bm.faces:
for loop in face.loops:
if loop[uv_layers].select:
settings.selection_uv_loops.append( [face.index, loop.vert.index] )
#Face selections (Loops)
settings.selection_uv_loops = []
for face in bm.faces:
for loop in face.loops:
if loop[uv_layers].select:
settings.selection_uv_loops.append( [face.index, loop.vert.index] )
def selection_restore(bm = None, uv_layers = None):
if bpy.context.object.mode != 'EDIT':
bpy.ops.object.mode_set(mode = 'EDIT')
if bpy.context.object.mode != 'EDIT':
bpy.ops.object.mode_set(mode = 'EDIT')
if not bm:
bm = bmesh.from_edit_mesh(bpy.context.active_object.data);
if not uv_layers:
uv_layers = bm.loops.layers.uv.verify();
if not bm:
bm = bmesh.from_edit_mesh(bpy.context.active_object.data);
if not uv_layers:
uv_layers = bm.loops.layers.uv.verify();
# print("selectionRestore")
bpy.context.scene.tool_settings.uv_select_mode = settings.selection_uv_mode
bpy.context.tool_settings.transform_pivot_point = settings.selection_uv_pivot
# print("selectionRestore")
bpy.context.scene.tool_settings.uv_select_mode = settings.selection_uv_mode
bpy.context.tool_settings.transform_pivot_point = settings.selection_uv_pivot
contextViewUV = utilities_ui.GetContextViewUV()
if contextViewUV:
bpy.ops.uv.cursor_set(contextViewUV, location=settings.selection_uv_pivot_pos)
contextViewUV = utilities_ui.GetContextViewUV()
if contextViewUV:
bpy.ops.uv.cursor_set(contextViewUV, location=settings.selection_uv_pivot_pos)
bpy.ops.mesh.select_all(action='DESELECT')
bpy.ops.mesh.select_all(action='DESELECT')
if hasattr(bm.verts, "ensure_lookup_table"):
bm.verts.ensure_lookup_table()
# bm.edges.ensure_lookup_table()
bm.faces.ensure_lookup_table()
if hasattr(bm.verts, "ensure_lookup_table"):
bm.verts.ensure_lookup_table()
# bm.edges.ensure_lookup_table()
bm.faces.ensure_lookup_table()
#FACE selection
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE')
for index in settings.selection_face_indexies:
if index < len(bm.faces):
bm.faces[index].select = True
#FACE selection
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE')
for index in settings.selection_face_indexies:
if index < len(bm.faces):
bm.faces[index].select = True
#VERT Selection
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT')
for index in settings.selection_vert_indexies:
if index < len(bm.verts):
bm.verts[index].select = True
#VERT Selection
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT')
for index in settings.selection_vert_indexies:
if index < len(bm.verts):
bm.verts[index].select = True
#Selection Mode
bpy.context.scene.tool_settings.mesh_select_mode = settings.selection_mode
#Selection Mode
bpy.context.scene.tool_settings.mesh_select_mode = settings.selection_mode
#UV Face-UV Selections (Loops)
bpy.ops.uv.select_all(contextViewUV, action='DESELECT')
for uv_set in settings.selection_uv_loops:
for loop in bm.faces[ uv_set[0] ].loops:
if loop.vert.index == uv_set[1]:
loop[uv_layers].select = True
break
#UV Face-UV Selections (Loops)
bpy.ops.uv.select_all(contextViewUV, action='DESELECT')
for uv_set in settings.selection_uv_loops:
for loop in bm.faces[ uv_set[0] ].loops:
if loop.vert.index == uv_set[1]:
loop[uv_layers].select = True
break
bpy.context.view_layer.update()
bpy.context.view_layer.update()
def get_selected_faces():
bm = bmesh.from_edit_mesh(bpy.context.active_object.data);
faces = [];
for face in bm.faces:
if face.select:
faces.append(face)
bm = bmesh.from_edit_mesh(bpy.context.active_object.data);
faces = [];
for face in bm.faces:
if face.select:
faces.append(face)
return faces
return faces
def set_selected_faces(faces):
bm = bmesh.from_edit_mesh(bpy.context.active_object.data);
uv_layers = bm.loops.layers.uv.verify();
for face in faces:
for loop in face.loops:
loop[uv_layers].select = True
bm = bmesh.from_edit_mesh(bpy.context.active_object.data);
uv_layers = bm.loops.layers.uv.verify();
for face in faces:
for loop in face.loops:
loop[uv_layers].select = True
def get_selected_uvs(bm, uv_layers):
"""Returns selected mesh vertices of selected UV's"""
uvs = []
for face in bm.faces:
if face.select:
for loop in face.loops:
if loop[uv_layers].select:
uvs.append( loop[uv_layers] )
return uvs
"""Returns selected mesh vertices of selected UV's"""
uvs = []
for face in bm.faces:
if face.select:
for loop in face.loops:
if loop[uv_layers].select:
uvs.append( loop[uv_layers] )
return uvs
def get_selected_uv_verts(bm, uv_layers):
"""Returns selected mesh vertices of selected UV's"""
verts = set()
for face in bm.faces:
if face.select:
for loop in face.loops:
if loop[uv_layers].select:
verts.add( loop.vert )
return list(verts)
"""Returns selected mesh vertices of selected UV's"""
verts = set()
for face in bm.faces:
if face.select:
for loop in face.loops:
if loop[uv_layers].select:
verts.add( loop.vert )
return list(verts)
def get_selected_uv_edges(bm, uv_layers):
"""Returns selected mesh edges of selected UV's"""
verts = get_selected_uv_verts(bm, uv_layers)
edges = []
for edge in bm.edges:
if edge.verts[0] in verts and edge.verts[1] in verts:
edges.append(edge)
return edges
"""Returns selected mesh edges of selected UV's"""
verts = get_selected_uv_verts(bm, uv_layers)
edges = []
for edge in bm.edges:
if edge.verts[0] in verts and edge.verts[1] in verts:
edges.append(edge)
return edges
def get_selected_uv_faces(bm, uv_layers):
"""Returns selected mesh faces of selected UV's"""
faces = []
for face in bm.faces:
if face.select:
count = 0
for loop in face.loops:
if loop[uv_layers].select:
count+=1
if count == len(face.loops):
faces.append(face)
return faces
"""Returns selected mesh faces of selected UV's"""
faces = []
for face in bm.faces:
if face.select:
count = 0
for loop in face.loops:
if loop[uv_layers].select:
count+=1
if count == len(face.loops):
faces.append(face)
return faces
def get_vert_to_uv(bm, uv_layers):
vert_to_uv = {}
for face in bm.faces:
for loop in face.loops:
vert = loop.vert
uv = loop[uv_layers]
if vert not in vert_to_uv:
vert_to_uv[vert] = [uv];
else:
vert_to_uv[vert].append(uv)
return vert_to_uv
vert_to_uv = {}
for face in bm.faces:
for loop in face.loops:
vert = loop.vert
uv = loop[uv_layers]
if vert not in vert_to_uv:
vert_to_uv[vert] = [uv];
else:
vert_to_uv[vert].append(uv)
return vert_to_uv
def get_uv_to_vert(bm, uv_layers):
uv_to_vert = {}
for face in bm.faces:
for loop in face.loops:
vert = loop.vert
uv = loop[uv_layers]
if uv not in uv_to_vert:
uv_to_vert[ uv ] = vert;
return uv_to_vert
uv_to_vert = {}
for face in bm.faces:
for loop in face.loops:
vert = loop.vert
uv = loop[uv_layers]
if uv not in uv_to_vert:
uv_to_vert[ uv ] = vert;
return uv_to_vert
def getSelectionBBox():
bm = bmesh.from_edit_mesh(bpy.context.active_object.data);
uv_layers = bm.loops.layers.uv.verify();
bbox = {}
boundsMin = Vector((99999999.0,99999999.0))
boundsMax = Vector((-99999999.0,-99999999.0))
boundsCenter = Vector((0.0,0.0))
countFaces = 0;
for face in bm.faces:
if face.select:
for loop in face.loops:
if loop[uv_layers].select is True:
uv = loop[uv_layers].uv
boundsMin.x = min(boundsMin.x, uv.x)
boundsMin.y = min(boundsMin.y, uv.y)
boundsMax.x = max(boundsMax.x, uv.x)
boundsMax.y = max(boundsMax.y, uv.y)
boundsCenter+= uv
countFaces+=1
bbox['min'] = boundsMin
bbox['max'] = boundsMax
bbox['width'] = (boundsMax - boundsMin).x
bbox['height'] = (boundsMax - boundsMin).y
if countFaces == 0:
bbox['center'] = boundsMin
else:
bbox['center'] = boundsCenter / countFaces
bbox['area'] = bbox['width'] * bbox['height']
bbox['minLength'] = min(bbox['width'], bbox['height'])
return bbox;
bm = bmesh.from_edit_mesh(bpy.context.active_object.data);
uv_layers = bm.loops.layers.uv.verify();
bbox = {}
boundsMin = Vector((99999999.0,99999999.0))
boundsMax = Vector((-99999999.0,-99999999.0))
boundsCenter = Vector((0.0,0.0))
countFaces = 0;
for face in bm.faces:
if face.select:
for loop in face.loops:
if loop[uv_layers].select is True:
uv = loop[uv_layers].uv
boundsMin.x = min(boundsMin.x, uv.x)
boundsMin.y = min(boundsMin.y, uv.y)
boundsMax.x = max(boundsMax.x, uv.x)
boundsMax.y = max(boundsMax.y, uv.y)
boundsCenter+= uv
countFaces+=1
bbox['min'] = boundsMin
bbox['max'] = boundsMax
bbox['width'] = (boundsMax - boundsMin).x
bbox['height'] = (boundsMax - boundsMin).y
if countFaces == 0:
bbox['center'] = boundsMin
else:
bbox['center'] = boundsCenter / countFaces
bbox['area'] = bbox['width'] * bbox['height']
bbox['minLength'] = min(bbox['width'], bbox['height'])
return bbox;
def getSelectionIslands(bm=None, uv_layers=None):
if bm == None:
bm = bmesh.from_edit_mesh(bpy.context.active_object.data)
uv_layers = bm.loops.layers.uv.verify()
if bm == None:
bm = bmesh.from_edit_mesh(bpy.context.active_object.data)
uv_layers = bm.loops.layers.uv.verify()
#Reference A: https://github.com/nutti/Magic-UV/issues/41
#Reference B: https://github.com/c30ra/uv-align-distribute/blob/v2.2/make_island.py
#Reference A: https://github.com/nutti/Magic-UV/issues/41
#Reference B: https://github.com/c30ra/uv-align-distribute/blob/v2.2/make_island.py
#Extend selection
if bpy.context.scene.tool_settings.use_uv_select_sync == False:
bpy.ops.uv.select_linked()
#Extend selection
if bpy.context.scene.tool_settings.use_uv_select_sync == False:
bpy.ops.uv.select_linked()
#Collect selected UV faces
faces_selected = [];
for face in bm.faces:
if face.select and face.loops[0][uv_layers].select:
faces_selected.append(face)
#Collect UV islands
# faces_parsed = []
faces_unparsed = faces_selected.copy()
islands = []
for face in faces_selected:
if face in faces_unparsed:
#Select single face
bpy.ops.uv.select_all(action='DESELECT')
face.loops[0][uv_layers].select = True;
bpy.ops.uv.select_linked()#Extend selection
#Collect faces
islandFaces = [face];
for faceAll in faces_unparsed:
if faceAll != face and faceAll.select and faceAll.loops[0][uv_layers].select:
islandFaces.append(faceAll)
for faceAll in islandFaces:
faces_unparsed.remove(faceAll)
#Assign Faces to island
islands.append(islandFaces)
#Restore selection
# for face in faces_selected:
# for loop in face.loops:
# loop[uv_layers].select = True
print("Islands: {}x".format(len(islands)))
return islands
#Collect selected UV faces
faces_selected = [];
for face in bm.faces:
if face.select and face.loops[0][uv_layers].select:
faces_selected.append(face)
#Collect UV islands
# faces_parsed = []
faces_unparsed = faces_selected.copy()
islands = []
for face in faces_selected:
if face in faces_unparsed:
#Select single face
bpy.ops.uv.select_all(action='DESELECT')
face.loops[0][uv_layers].select = True;
bpy.ops.uv.select_linked()#Extend selection
#Collect faces
islandFaces = [face];
for faceAll in faces_unparsed:
if faceAll != face and faceAll.select and faceAll.loops[0][uv_layers].select:
islandFaces.append(faceAll)
for faceAll in islandFaces:
faces_unparsed.remove(faceAll)
#Assign Faces to island
islands.append(islandFaces)
#Restore selection
# for face in faces_selected:
# for loop in face.loops:
# loop[uv_layers].select = True
print("Islands: {}x".format(len(islands)))
return islands

Loading…
Cancel
Save