-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathRobloxFollowerRig
160 lines (124 loc) · 5.38 KB
/
RobloxFollowerRig
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
bl_info = {
"name": "Roblox Follower Rig",
"author": "Jax",
"version": (1, 2),
"blender": (4, 0, 0),
"location": "View3D > Jax's Stuff",
"description": "Set up Roblox Follower Rig with custom export and material consolidation",
"category": "Rigging",
}
import bpy
from bpy.types import Operator, Panel
from bpy.props import BoolProperty
VALID_NAMES = {"LeftLeg", "RightLeg", "RightArm", "LeftArm", "Head", "Torso"}
def consolidate_materials(obj):
if len(obj.material_slots) > 0:
# Keep the first material and rename it
first_material = obj.material_slots[0].material
first_material.name = "RobloxMat"
# Remove all other materials
for i in range(len(obj.material_slots) - 1, 0, -1):
obj.data.materials.pop(index=i)
# Assign the remaining material to all faces
for polygon in obj.data.polygons:
polygon.material_index = 0
else:
# If no materials, create a new one
mat = bpy.data.materials.new(name="RobloxMat")
obj.data.materials.append(mat)
class ROBLOX_OT_follower_rig(Operator):
bl_idname = "object.roblox_follower_rig"
bl_label = "Apply Meshes to Rig"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
armature = bpy.data.objects.get("Armature")
if armature is None or armature.type != 'ARMATURE':
self.report({'ERROR'}, "No armature named 'Armature' found in the scene")
return {'CANCELLED'}
torso = None
parts_to_join = []
for obj in bpy.context.selected_objects:
if obj.type != 'MESH' or obj.name not in VALID_NAMES:
continue
if obj.name == "Torso":
torso = obj
else:
parts_to_join.append(obj)
if not torso:
self.report({'ERROR'}, "No object named 'Torso' found")
return {'CANCELLED'}
for obj in parts_to_join + [torso]:
if obj.parent == armature:
obj.parent = None
bpy.ops.object.select_all(action='DESELECT')
obj.select_set(True)
armature.select_set(True)
bpy.context.view_layer.objects.active = armature
bpy.ops.object.parent_set(type='ARMATURE_AUTO')
for vg in obj.vertex_groups:
obj.vertex_groups.remove(vg)
vertex_group = obj.vertex_groups.new(name=obj.name)
vertex_group.add(range(len(obj.data.vertices)), 1.0, 'REPLACE')
bpy.ops.object.select_all(action='DESELECT')
for obj in parts_to_join:
obj.select_set(True)
torso.select_set(True)
bpy.context.view_layer.objects.active = torso
bpy.ops.object.join()
torso.name = "Avatar"
if torso.parent != armature:
torso.parent = armature
torso.parent_type = 'ARMATURE'
modifier = torso.modifiers.get("Armature")
if not modifier:
modifier = torso.modifiers.new(name="Armature", type='ARMATURE')
modifier.object = armature
modifier.use_vertex_groups = True
# Consolidate materials
consolidate_materials(torso)
self.report({'INFO'}, "Roblox Follower Rig setup complete")
if context.scene.roblox_export_when_done:
bpy.ops.object.select_all(action='DESELECT')
armature.select_set(True)
torso.select_set(True)
bpy.context.view_layer.objects.active = armature
bpy.ops.export_scene.fbx(
'INVOKE_DEFAULT',
object_types={'ARMATURE', 'MESH'},
use_selection=True,
apply_scale_options='FBX_SCALE_ALL'
)
return {'FINISHED'}
class VIEW3D_PT_roblox_follower_rig(Panel):
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = "Jax's Stuff"
bl_label = "Roblox Follower Rig"
def draw(self, context):
layout = self.layout
armature = bpy.data.objects.get("Armature")
if armature and armature.select_get():
layout.label(text="Armature: Found", icon='CHECKMARK')
else:
layout.label(text="Armature: Not Found", icon='ERROR')
selected_names = {obj.name for obj in context.selected_objects if obj.type == 'MESH'}
missing_parts = VALID_NAMES - selected_names
layout.label(text=f"Parts: {len(selected_names)}/{len(VALID_NAMES)} Found", icon='INFO')
if missing_parts:
layout.label(text=f"Missing: {', '.join(missing_parts)}", icon='ERROR')
layout.operator("object.roblox_follower_rig")
layout.prop(context.scene, "roblox_export_when_done")
def register():
bpy.types.Scene.roblox_export_when_done = BoolProperty(
name="Export when done",
description="Open FBX export dialogue after applying rig",
default=False
)
bpy.utils.register_class(ROBLOX_OT_follower_rig)
bpy.utils.register_class(VIEW3D_PT_roblox_follower_rig)
def unregister():
del bpy.types.Scene.roblox_export_when_done
bpy.utils.unregister_class(VIEW3D_PT_roblox_follower_rig)
bpy.utils.unregister_class(ROBLOX_OT_follower_rig)
if __name__ == "__main__":
register()