Last active
November 5, 2024 05:10
-
-
Save tinkerer-red/6905635eec8422bc7aa0fa4cb710a524 to your computer and use it in GitHub Desktop.
A collection of blender scripts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Purpose is to bind all materials to a single object to allow for importing into unity, as unity will not import a material if it's not actively used on a model, despite being in the files. | |
import bpy | |
def create_carrier_mesh(): | |
# Name for the carrier mesh | |
carrier_mesh_name = "Material_Carrier_Mesh" | |
# Create a new mesh and object for the carrier | |
mesh_data = bpy.data.meshes.new(carrier_mesh_name) | |
carrier_obj = bpy.data.objects.new(carrier_mesh_name, mesh_data) | |
# Link the carrier object to the current scene | |
bpy.context.collection.objects.link(carrier_obj) | |
# Debug: Print all keys in carrier_obj.data to locate material-related attributes | |
print("carrier_obj.data keys:") | |
for attribute in dir(carrier_obj.data): | |
print(attribute) | |
# Store vertices and faces for the polygons | |
vertices = [] | |
faces = [] | |
material_indices = [] | |
# Size of each polygon | |
poly_size = 0.1 | |
offset = 0 | |
# Iterate over each material in the Blender file | |
for idx, material in enumerate(bpy.data.materials): | |
# Define vertices for a square polygon for each material | |
v_start = len(vertices) | |
vertices.extend([ | |
(offset, 0, 0), | |
(offset + poly_size, 0, 0), | |
(offset + poly_size, poly_size, 0), | |
(offset, poly_size, 0) | |
]) | |
faces.append((v_start, v_start + 1, v_start + 2, v_start + 3)) | |
material_indices.append(idx) | |
# Add the material to the carrier object | |
if material.name not in carrier_obj.data.materials: | |
carrier_obj.data.materials.append(material) | |
# Move offset for the next polygon to avoid overlap | |
offset += poly_size * 2 | |
# Assign vertices, faces, and materials to the mesh data | |
mesh_data.from_pydata(vertices, [], faces) | |
mesh_data.update() | |
# Set the material index for each face to match the corresponding material | |
for face, mat_index in zip(carrier_obj.data.polygons, material_indices): | |
face.material_index = mat_index | |
print(f"Carrier mesh '{carrier_mesh_name}' created with {len(bpy.data.materials)} materials.") | |
def export_carrier_mesh(filepath): | |
# Ensure forward slashes in filepath to avoid issues with backslashes | |
filepath = filepath.replace("\\", "/") | |
# Create the carrier mesh with all materials | |
create_carrier_mesh() | |
# Select and export the carrier mesh as FBX | |
bpy.ops.object.select_all(action='DESELECT') | |
bpy.data.objects["Material_Carrier_Mesh"].select_set(True) | |
bpy.context.view_layer.objects.active = bpy.data.objects["Material_Carrier_Mesh"] | |
# Export as FBX | |
bpy.ops.export_scene.fbx( | |
filepath=filepath, | |
use_selection=True, | |
apply_unit_scale=True, | |
bake_space_transform=True | |
) | |
print(f"Exported carrier mesh to '{filepath}'.") | |
# Example usage: specify the path where you want to export the FBX | |
output_path = "E:\\vrc\\Rust Player Model\\MaterialCarrierMesh.fbx" # Replace with your desired output path | |
export_carrier_mesh(output_path) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Usage Instructions | |
# Select All Source Objects First: In the 3D Viewport, select all the source meshes (hair models). | |
# Select the Target Object Last: The last selected (active) object will be treated as the target where the shape keys will be added. | |
# Run the Script: This will create a new shape key on the target for each source object, with deformations applied per vertex based on UV proximity. | |
import bpy | |
import bmesh | |
from mathutils import Vector | |
# Function to find exact matches between vertices | |
def find_exact_matches(source_verts, target_verts): | |
print(" - Finding exact matches between source and target vertices...") | |
unmatched_source = [] | |
unchanged_target = set() # Set to track target vertices with exact matches | |
unmatched_target = set(target_verts) # Set to track vertices that need matching | |
for i, source_vert in enumerate(source_verts): | |
if i % 100 == 0: | |
print(f" - Processed {i}/{len(source_verts)} source vertices for exact matches...") | |
matched = False | |
for target_vert in target_verts: | |
if (source_vert.co - target_vert.co).length == 0: | |
unchanged_target.add(target_vert) # Mark target vertex as exact match | |
unmatched_target.discard(target_vert) # Remove from unmatched target set | |
matched = True | |
break | |
if not matched: | |
unmatched_source.append(source_vert) # Track only unmatched source vertices | |
print(" - Exact match search complete.") | |
return unmatched_source, list(unmatched_target), unchanged_target | |
# Function to group vertices by UV location, limited to unmatched vertices | |
def group_by_uv(mesh_obj, unmatched_verts): | |
print(f" - Grouping vertices by UV location for {mesh_obj.name}...") | |
uv_layer = mesh_obj.data.uv_layers.active.data | |
uv_groups = {} | |
for poly in mesh_obj.data.polygons: | |
for loop_index in poly.loop_indices: | |
uv_coord = tuple(uv_layer[loop_index].uv) | |
vert_index = mesh_obj.data.loops[loop_index].vertex_index | |
vert = mesh_obj.data.vertices[vert_index] | |
if vert in unmatched_verts: # Only group unmatched vertices | |
if uv_coord not in uv_groups: | |
uv_groups[uv_coord] = [] | |
uv_groups[uv_coord].append(vert) | |
print(f" - UV grouping complete with {len(uv_groups)} unique UV locations.") | |
return uv_groups | |
# Function to find the best nearest vertex pairs within UV-matching groups with proper pairing tracking | |
def find_best_nearest(source_group, target_group): | |
print(" - Finding nearest matching vertices within UV groups...") | |
paired_verts = {} | |
paired_targets = set() # Track paired target vertices | |
paired_sources = set() # Track paired source vertices | |
for i, src_vert in enumerate(source_group): | |
if i % 10 == 0: | |
print(f" - Processed {i}/{len(source_group)} source vertices in UV group...") | |
# Skip if the source vertex is already paired | |
if src_vert in paired_sources: | |
continue | |
# Reset closest tracking for each source vertex | |
closest_vert = None | |
min_distance = float("inf") | |
for tgt_vert in target_group: | |
# Skip if this target vertex has already been paired | |
if tgt_vert in paired_targets: | |
continue | |
# Calculate the distance between source and target vertices | |
dist = (src_vert.co - tgt_vert.co).length | |
# If this distance is the smallest encountered, update closest_vert | |
if dist < min_distance: | |
min_distance = dist | |
closest_vert = tgt_vert | |
# Pair the source and target vertices if a closest match was found | |
if closest_vert: | |
print(f"Closest Target for Source {src_vert.index} is Target {closest_vert.index} with Distance: {min_distance}") | |
paired_verts[src_vert] = closest_vert | |
paired_targets.add(closest_vert) # Mark the target vertex as paired | |
paired_sources.add(src_vert) # Mark the source vertex as paired | |
print(" - Nearest neighbor pairing complete.") | |
return paired_verts | |
# Function to adaptively move unmatched, unchanged vertices | |
def adapt_unmatched_unchanged(target_shape_key, unmatched_unchanged, source_group): | |
print(" - Adapting unmatched, unchanged vertices with average movement of nearest neighbors...") | |
for tgt_vert in unmatched_unchanged: | |
# Find the 3 closest source vertices based on distance | |
distances = [(src_vert, (tgt_vert.co - src_vert.co).length) for src_vert in source_group] | |
distances.sort(key=lambda x: x[1]) # Sort by distance | |
# Get the 3 nearest vertices | |
nearest_three = distances[:3] | |
# Initialize the offset as a Vector and calculate the average offset based on the 3 nearest vertices | |
average_offset = Vector((0, 0, 0)) | |
for src_vert, _ in nearest_three: | |
average_offset += (src_vert.co - tgt_vert.co) | |
average_offset /= len(nearest_three) # Divide by 3 to get the average | |
# Apply the offset to the target vertex in the shape key | |
target_shape_key.data[tgt_vert.index].co = tgt_vert.co + average_offset | |
print(" - Adaptive movement for unmatched, unchanged vertices complete.") | |
# Step 1: Set the target object as the active object, assuming it's the last selected object | |
target_obj = bpy.context.active_object | |
print(f"Target object for shape key transfer: {target_obj.name}") | |
# Step 2: Iterate over all selected objects, excluding the target | |
for source_obj in bpy.context.selected_objects: | |
if source_obj == target_obj: | |
continue | |
print(f"Processing source object: {source_obj.name}") | |
# Step 3: Get vertices and remove exact matches | |
source_verts = source_obj.data.vertices | |
target_verts = target_obj.data.vertices | |
source_unmatched, target_unmatched, unchanged_target = find_exact_matches(source_verts, target_verts) | |
# Step 4: Group unmatched vertices by UV location (only unmatched vertices are grouped) | |
source_uv_groups = group_by_uv(source_obj, set(source_unmatched)) | |
target_uv_groups = group_by_uv(target_obj, set(target_unmatched)) | |
# Step 5: Create a new shape key for each source object deformation | |
shape_key_name = f"TransferredDeform_{source_obj.name}" | |
print(f" - Creating new shape key: {shape_key_name}") | |
target_obj.shape_key_add(name=shape_key_name, from_mix=False) | |
target_shape_key = target_obj.data.shape_keys.key_blocks[shape_key_name] | |
# Step 6: Match vertices within UV groups and apply offsets | |
paired_verts = {} | |
for uv_coord, source_group in source_uv_groups.items(): | |
if uv_coord in target_uv_groups: | |
target_group = target_uv_groups[uv_coord] | |
matched_pairs = find_best_nearest(source_group, target_group) | |
paired_verts.update(matched_pairs) | |
# Apply the nearest matching as shape key deformation | |
for src_vert, tgt_vert in matched_pairs.items(): | |
if tgt_vert not in unchanged_target: # Skip vertices with exact matches | |
offset = src_vert.co - tgt_vert.co | |
target_shape_key.data[tgt_vert.index].co = tgt_vert.co + offset | |
print(f" - Applied deformation for UV group: {uv_coord}") | |
# Step 7: Adapt unmatched, unchanged vertices based on the 3 nearest neighbors | |
unmatched_unchanged = [tgt_vert for tgt_vert in target_verts if tgt_vert not in paired_verts.values() and tgt_vert not in unchanged_target] | |
adapt_unmatched_unchanged(target_shape_key, unmatched_unchanged, source_verts) | |
print(f"Completed shape key transfer for source object: {source_obj.name}") | |
print("Shape key transfer complete for all selected source objects.") |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Prints a list of users who are making use of a material, typically used if you are cleaning up a project with many oddly named materials and wish to get a comprahensive list to update assets | |
import bpy | |
def list_material_usage(): | |
# Iterate over each material in the file | |
for material in bpy.data.materials: | |
print(f"Material: {material.name}") | |
# Find all objects that use this material | |
users = [] | |
for obj in bpy.data.objects: | |
if obj.type == 'MESH': | |
# Check each material slot in the object | |
for slot in obj.material_slots: | |
if slot.material == material: | |
users.append(obj.name) | |
break # Move to the next object once the material is found | |
# Display the results | |
if users: | |
print(f" Used by objects: {', '.join(users)}") | |
else: | |
print(" Not used by any objects.") | |
# Run the debug script | |
list_material_usage() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Select all objects you wish to fix weights for and press run | |
# This script is used for making sure verts which share a position bart are not connected have the same weights to prevent seems from showing between seporate parts | |
import bpy | |
from collections import defaultdict | |
def average_weights(weight_dict): | |
"""Average weights from multiple vertices and normalize them.""" | |
total_weights = defaultdict(float) | |
# Sum weights for each bone | |
for weights in weight_dict.values(): | |
for group, weight in weights.items(): | |
total_weights[group] += weight | |
# Average the weights by dividing by the number of vertices | |
vertex_count = len(weight_dict) | |
for group in total_weights: | |
total_weights[group] /= vertex_count | |
# Normalize weights to ensure they sum to 1 | |
total_weight = sum(total_weights.values()) | |
if total_weight > 0: | |
for group in total_weights: | |
total_weights[group] /= total_weight | |
return total_weights | |
def apply_weights_to_vertex(vertex, weight_dict, obj): | |
"""Apply averaged weights to a vertex.""" | |
# Clear all weights by setting existing weights to zero | |
for group in vertex.groups: | |
group.weight = 0.0 | |
# Assign new weights, adding vertex group if necessary | |
for group_index, weight in weight_dict.items(): | |
# Ensure the vertex is in the target group, adding it if not | |
if not any(g.group == group_index for g in vertex.groups): | |
obj.vertex_groups[group_index].add([vertex.index], 0.0, 'REPLACE') | |
# Set the weight for the group | |
for g in vertex.groups: | |
if g.group == group_index: | |
g.weight = weight | |
break | |
def average_vertex_weights_for_mesh(obj): | |
"""Find vertices with identical positions and average their weights.""" | |
print(f"Processing mesh: {obj.name}") | |
# Dictionary to store vertices by exact positions | |
position_dict = defaultdict(list) | |
modified_pairs = 0 | |
# Populate position_dict with vertices grouped by exact position | |
for vert in obj.data.vertices: | |
pos_key = tuple(obj.matrix_world @ vert.co) # Exact position as the key | |
position_dict[pos_key].append(vert) | |
# Average and update weights for each group of vertices at the same position | |
for verts in position_dict.values(): | |
if len(verts) > 1: # Only average if there are multiple vertices at the same position | |
modified_pairs += 1 | |
weight_dict = defaultdict(dict) | |
for vert in verts: | |
for group in vert.groups: | |
weight_dict[vert.index][group.group] = group.weight | |
# Calculate the averaged weights | |
averaged_weights = average_weights(weight_dict) | |
# Apply the averaged weights to each vertex in the group | |
for vert in verts: | |
apply_weights_to_vertex(vert, averaged_weights, obj) | |
print(f"Completed processing for mesh: {obj.name} - Modified {modified_pairs} pairs of vertices.") | |
# Run the script on all selected mesh objects | |
for obj in bpy.context.selected_objects: | |
if obj.type == 'MESH' and obj.vertex_groups: | |
average_vertex_weights_for_mesh(obj) | |
print("Weight averaging complete for all selected meshes.") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment