RiggityPivot: Make Custom Rig from Pivot Points and Parent-Child Hierarchy

Hi Dev Community,

I was toying with the idea of using pivot points and a parent-child hierarchy to make custom rigs and wanted to post the converter (which I’m calling “Riggity”) here in case someone other than me finds the idea useful.

The basic idea:

  1. Create a folder or model to house the hierarchy
  2. Create a single BasePart to function as the root of the hierarchy
  3. Parent Parts/MeshParts into the hierarchy and set joint locations by moving Pivot Points (using Pivot Editor)
  4. Select the model/folder to convert and click the “Run Scripts” button on the MODEL tab (far right end)
  5. Navigate to the place where you stored the provided script and press “Open”
  6. Check Output window for any messages
  7. Animate your model in the usual way

Notes:

  • It uses your edited pivot points to place attachments, which Humanoid:BuildRigFromAttachments then uses to do the real work.
  • It will do some other stuff like set anchors and weld parts if named with leading “__” or “_” (see comments in script)
  • It’s non destructive since it works from a clone of your source model (new model will be placed in same location as orig)
  • Non BasePart objects should be copied to the new model more or less where you’d expect
  • I tried to give it useful warning messages, so check the output if it isn’t doing what you want

Copy/paste the provided code to a Script and Right-click in Explorer to save to your drive. (Someone feel free verify the safety of using this code in that way for those who aren’t comfortable making that determination on their own).

Script
--[[

	Riggity - Easy Rig Builder Using Pivots and Parent-Child Hierarchy (Version 0.2)

	USE:
		* Parent parts together in Studio and use native Pivot Editor to set joint locations.
			- Place this hierarchy inside a folder or model
		* Then run this script to convert the hierarchy of parts (w Pivot Edits) into an animatable model.
			- Can run script using Studios "Run Script" button (far right side of MODEL tab)

	Example Layout:
	Folder (or Model)
		[Can have other stuff parented here besides root BasePart as long as they aren't also BaseParts]
		Root (Should be only BasePart child the Folder (or Model) - will become PrimaryPart of Model)
			Body
				LArm
				RArm	
				Neck
					Head
						__Eye (names starting with "__" will be welded to parent and not re-parented)
						__Eye
						__Ear
						__Ear
				LLeg
				RLeg
				_Spike (names starting with '_' will be welded to parent (Body) and re-parented to model)

				etc

	NOTES: 
		* THERE SHOULD BE ONLY ONE BasePart object (Part, MeshPart, etc) parented to the selected folder (or model)
			- The script drills down through a hierarchy of BaseParts and there can be only one at the root
			- Other items in the selected folder (or model) that are not BaseParts will be copied to fin model
		* PART NAMES SHOULD BE UNIQUE
			- Parts to be welded and parented to a part need to have names starting with "__"
			- Parts with names starting with "__" will be welded to their parent and not re-parented to the model
				- These are also set to CanCollide false, etc
			- Parts with names starting with "_" will be welded to their parent and reparented to model
		* If you put folders or models within the hierarchy, be sure to sel the parent folder before running the script
			- it will try to find folder or model ancestor as top of chain, so any you add could interrupt that process
		* Script creates a clone so doesn't change your original src hierarchy

	By Astr0Derp 2022
]]

--- Setup ---

-- set this to false if you want a Humanoid instead of an AnimationController in fin model
local USE_ANIMATION_CONTROLLER = true

-- change this if you don't want root part set to invisible
local ROOT_IS_INVISIBLE = true

-- change this if you don't want the model parented to workspace until end of processing
-- if there is a problem the resulting partially complete model can be used for troubleshooting
-- setting to false will wait to parent the model until the very end (showing nothing if it fails)
local PARENT_MODEL_EARLY = true


--- Main ---

local selected = game.Selection:Get()[1]
local selection
if selected:IsA("Folder") or selected:IsA("Model") then
	selection = selected
else
	selection = selected:FindFirstAncestorOfClass("Folder")
	if not selection then
		selection = selected:FindFirstAncestorOfClass("Model")
	end
end

if selection == nil then
	warn("Riggity: **FAILED!** \nPlease select a model or parent folder containing parts to rig.")
	selected = {}
	game.Selection:Set(selected)
	return
elseif selection:FindFirstChildOfClass("Humanoid") ~= nil or 
	selection:FindFirstChildOfClass("AnimationController") ~= nil then
	warn("Riggity: **FAILED!** \nThis model already has a Humanoid (or AnimationController).")
	selected = {}
	game.Selection:Set(selected)
	return
end

local TMP_FOLDER = "riggity_tmp"
local model = Instance.new("Model")
model.Name = selection.Name
if PARENT_MODEL_EARLY then
	model.Parent = workspace
end

-- let's clone the selection instead of messing with the original
local root = selection:FindFirstChildWhichIsA("BasePart"):Clone()
local nextItem = root

while nextItem do

	-- DFS looking for BaseParts with BasePart children
	local BasePartchild = nextItem:FindFirstChildWhichIsA("BasePart")
	if BasePartchild == nil then
		-- nextItem is a leafNode
		local prevItem = nextItem.Parent
		local leafNode = nextItem

		if leafNode ~= root then
			if leafNode.Name:match("^__") then
				-- is sub-part lvl weld
				leafNode.CanCollide = false
				leafNode.CanQuery = false
				leafNode.CanTouch = false
				if leafNode:FindFirstChildOfClass("WeldConstraint") == nil then
					local weld = Instance.new("WeldConstraint")
					weld.Part0 = leafNode.Parent
					weld.Part1 = leafNode
					weld.Parent = leafNode
				end
				-- put in temp folder to not break DFS
				local tmp = leafNode.Parent:FindFirstChild(TMP_FOLDER)
				if tmp == nil then
					tmp = Instance.new("Folder")
					tmp.Name = TMP_FOLDER
					tmp.Parent = leafNode.Parent
				end
				leafNode.Name = leafNode.Name:sub(3)
				leafNode.Anchored = false
				-- move to tmp folder
				leafNode.Parent = tmp
			elseif leafNode.Name:match("^_") then
				-- is model lvl weld
				if leafNode:FindFirstChildOfClass("WeldConstraint") == nil then
					local weld = Instance.new("WeldConstraint")
					weld.Part0 = leafNode.Parent
					weld.Part1 = leafNode
					weld.Parent = leafNode
				end
				leafNode.Name = nextItem.Name:sub(2)
				leafNode.Anchored = false
				-- move to model
				leafNode.Parent = model
			else
				-- not a weld, so make part of rig
				local att = Instance.new("Attachment")
				att.Name = leafNode.Name .. "RigAttachment"
				att.Parent = leafNode
				att.WorldCFrame = leafNode.CFrame * leafNode.PivotOffset

				att2 = att:Clone()
				att2.Parent = leafNode.Parent
				att2.CFrame = CFrame.new()
				att2.WorldCFrame = att.WorldCFrame

				-- unpack any tmp folder
				local tmp_folder = leafNode:FindFirstChild(TMP_FOLDER)
				if tmp_folder then
					for _, tmpItem in tmp_folder:GetChildren() do
						tmpItem.Parent = leafNode
					end
					tmp_folder:Destroy()
				end
				leafNode.Anchored = false
				
				if model:FindFirstChild(leafNode.Name) then
					warn("Riggity: **WARNING!** \nMore than one rigged part with same name: " .. leafNode.Name)
				end
				
				-- move to model
				leafNode.Parent = model
			end
			-- continue the DFS
			nextItem = prevItem
		else
			-- we're back at the root
			leafNode.Anchored = true
			leafNode.Parent = model
			nextItem = nil
		end
	else
		-- found child BasePart so keep drilling down
		nextItem = BasePartchild
	end
end

--[[
	Final step uses Humanoid:BuildRigFromAttachments(), so the model needs a 
	HumanoidRootPart as the PrimaryPart.
	
	Once the rig is built, the code below does this (not nec in this order)
	1. swaps out the Humanoid for an AnimationController (if UseAnimationController is true) 
		and sets the root name to what it was originally.
	2. sets model's PrimaryPart to root and makes it invis (if ROOT_IS_INVISIBLE == true)
	3. moves any remaining items that were in the original folder (or model) to the new model
	4. selects the new model in Studio
	5. and we're done
]]

local rootName = root.Name
root.Name = "HumanoidRootPart"
model.PrimaryPart = root
if not PARENT_MODEL_EARLY then
	model.Parent = workspace
end
if ROOT_IS_INVISIBLE then
	model.PrimaryPart.Transparency = 1
end

local bpCount = 0
for _, otherItem in selection:GetChildren() do
	if not otherItem:IsA("BasePart") then
		otherItem.Parent = model
	else
		bpCount += 1
		if bpCount > 1 then
			warn("Riggity: **WARNING!** \nThere was more than one ROOT in your selected model/folder. \nCheck your parenting and try again.")
			selected = {model}
			game.Selection:Set(selected)
			return	
		end
	end	
end

local hum = Instance.new("Humanoid")
hum.Parent = model
hum:BuildRigFromAttachments()

if USE_ANIMATION_CONTROLLER then
	hum:Destroy()
	local animController = Instance.new("AnimationController")
	animController.Parent = model
	model.PrimaryPart.Name = rootName
end

selected = {model}
game.Selection:Set(selected)

warn("Riggity: DONE! \nYour " .. model.Name .. " model is finished (See workspace).")

Here’s about 3 min of videos showing the rough process. The first two demonstrate construction of a very basic model, and the third converts it into something animatable. In the third video where I press the “Run Script” button, nothing shows in the video because it doesn’t show the pop-up window. At any rate, that’s where I navigate to the script and press “Open” to run it. Feel free to put all this through its paces and let me know if you run into issues with the script.



Enjoy!

6 Likes