Skip to content

Instantly share code, notes, and snippets.

@havchr
Last active February 4, 2025 09:08
Show Gist options
  • Save havchr/3fcbee65a8c3a7fe1391708ddda7e7e0 to your computer and use it in GitHub Desktop.
Save havchr/3fcbee65a8c3a7fe1391708ddda7e7e0 to your computer and use it in GitHub Desktop.
Unity3DS editor script, context menu for adjusting pivot of a parent object that have children placed around.
using UnityEditor;
using UnityEngine;
namespace Agens
{
#if UNITY_EDITOR
public enum PivotAlignment
{
Min,Mid,Max,
}
public class PivotAdjuster
{
/*
* PivotAdjuster adjusts the pivot based on childrens geometry/positions
* Good if you have a group of objects where someone has moved them locally instead of using the parent pivot,
* or to just tidy up.
*
* say hello @[email protected] (preffered)
* or @havchr.bsky.social on BlueSky
*
* License MIT, use as you please
*
*
*
* Usage click context menu for a game object in the hierarchy, - it generates a context menu
*
* like [MenuItem("GameObject/PivotAdjuster/X-Max/Y-Max/Z-Max", false, 30001)]
* Select the Pivot you want to have, and hopefully it will work.
* See bottom part of this file to see all permuations
*/
private static void AdjustPivot(MenuCommand command, PivotAlignment xAxis, PivotAlignment yAxis, PivotAlignment zAxis,bool preferMeshBounds=true)
{
var gob = command.context as GameObject;
Undo.SetCurrentGroupName("PivotAdjuster");
int undoGroupIndex = Undo.GetCurrentGroup();
Undo.RegisterFullObjectHierarchyUndo(gob,$"PivotAdjuster Undo {gob.name}");
var bounds = GetBoundsFromPositions(gob.transform);
if (preferMeshBounds)
{
GetBoundsFromMeshes(gob.transform,ref bounds);
}
//grab all children
var children = new Transform[gob.transform.childCount];
if (children.Length == 0)
{
//if we are childless - create a wrap, burrito.
GameObject wrap = new GameObject($"{gob.transform.name}_Wrap");
Undo.RegisterCreatedObjectUndo(wrap,$"PivotAdjuster Create Object Undo {wrap.name}");
wrap.transform.parent = gob.transform.parent;
gob.transform.parent = wrap.transform;
gob = wrap;
children = new Transform[gob.transform.childCount];
}
for (int childIndex = 0; childIndex < children.Length; childIndex++)
{
children[childIndex] = gob.transform.GetChild(childIndex);
}
//unparent all children.
for (int childIndex = 0; childIndex < children.Length; childIndex++)
{
var child = children[childIndex];
child.parent = null;
}
gob.transform.position = GetPosition(bounds, xAxis, yAxis, zAxis);
//reparent
for (int childIndex = 0; childIndex < children.Length; childIndex++)
{
var child = children[childIndex];
child.parent = gob.transform;
}
Selection.activeTransform = gob.transform;
Undo.CollapseUndoOperations(undoGroupIndex);
}
private static Vector3 GetPosition(Bounds bounds, PivotAlignment xAxis, PivotAlignment yAxis, PivotAlignment zAxis)
{
Vector3 answer = Vector3.zero;
if (xAxis == PivotAlignment.Min)
{
answer.x = bounds.min.x;
}
if (yAxis == PivotAlignment.Min)
{
answer.y = bounds.min.y;
}
if (zAxis == PivotAlignment.Min)
{
answer.z = bounds.min.z;
}
if (xAxis == PivotAlignment.Max)
{
answer.x = bounds.max.x;
}
if (yAxis == PivotAlignment.Max)
{
answer.y = bounds.max.y;
}
if (zAxis == PivotAlignment.Max)
{
answer.z = bounds.max.z;
}
if (xAxis == PivotAlignment.Mid)
{
answer.x = Mathf.Lerp(bounds.min.x,bounds.max.x,0.5f);
}
if (yAxis == PivotAlignment.Mid)
{
answer.y = Mathf.Lerp(bounds.min.y,bounds.max.y,0.5f);
}
if (zAxis == PivotAlignment.Mid)
{
answer.z = Mathf.Lerp(bounds.min.z,bounds.max.z,0.5f);
}
return answer;
}
public static Bounds GetBoundsFromPositions(Transform node)
{
Vector3 negative = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue); //lower left corner
Vector3 positive = new Vector3(float.MinValue,float.MinValue,float.MinValue); //upper right corner
for (int childIndex = 0; childIndex < node.childCount; childIndex++)
{
var child = node.GetChild(childIndex);
//m.bounds
negative.x = child.position.x < negative.x ? child.position.x : negative.x;
negative.y = child.position.y < negative.y ? child.position.y : negative.y;
negative.z = child.position.z < negative.z ? child.position.z : negative.z;
positive.x = child.position.x > positive.x ? child.position.x : positive.x;
positive.y = child.position.y > positive.y ? child.position.y : positive.y;
positive.z = child.position.z > positive.z ? child.position.z : positive.z;
}
return new Bounds(Vector3.Lerp(negative,positive,0.5f), positive-negative);
}
public static bool GetBoundsFromMeshes(Transform node,ref Bounds bounds)
{
Vector3 negative = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue); //lower left corner
Vector3 positive = new Vector3(float.MinValue,float.MinValue,float.MinValue); //upper right corner
var rendies = node.GetComponentsInChildren<Renderer>();
if (rendies.Length == 0)
{
return false;
}
foreach (var rendy in rendies)
{
negative.x = rendy.bounds.min.x < negative.x ? rendy.bounds.min.x : negative.x;
negative.y = rendy.bounds.min.y < negative.y ? rendy.bounds.min.y : negative.y;
negative.z = rendy.bounds.min.z < negative.z ? rendy.bounds.min.z : negative.z;
positive.x = rendy.bounds.max.x > positive.x ? rendy.bounds.max.x : positive.x;
positive.y = rendy.bounds.max.y > positive.y ? rendy.bounds.max.y : positive.y;
positive.z = rendy.bounds.max.z > positive.z ? rendy.bounds.max.z : positive.z;
}
bounds = new Bounds(Vector3.Lerp(negative, positive, 0.5f), positive - negative);
return true;
}
/*
* This lists all the menu options that allow us to select our pivots
*/
//feel free to hide this region
#region different pivot options
[MenuItem("GameObject/PivotAdjuster/Bottom Left Corner Back", false, 30001)]
static void AdjustPivotBottomLeftNearestBack(MenuCommand command)
{
AdjustPivot(command, xAxis: PivotAlignment.Min, yAxis: PivotAlignment.Min, zAxis: PivotAlignment.Min);
}
[MenuItem("GameObject/PivotAdjuster/Bottom Right Corner Back", false, 30001)]
static void AdjustPivotBottomRightNearestBack(MenuCommand command)
{
AdjustPivot(command, xAxis: PivotAlignment.Max, yAxis: PivotAlignment.Min, zAxis: PivotAlignment.Min);
}
[MenuItem("GameObject/PivotAdjuster/Bottom Left Corner Front", false, 30001)]
static void AdjustPivotBottomLeftNearestFront(MenuCommand command)
{
AdjustPivot(command, xAxis: PivotAlignment.Min, yAxis: PivotAlignment.Min, zAxis: PivotAlignment.Max);
}
[MenuItem("GameObject/PivotAdjuster/Bottom Right Corner Front", false, 30001)]
static void AdjustPivotBottomRightNearestFront(MenuCommand command)
{
AdjustPivot(command, xAxis: PivotAlignment.Max, yAxis: PivotAlignment.Min, zAxis: PivotAlignment.Max);
}
[MenuItem("GameObject/PivotAdjuster/Center All", false, 30001)]
static void AdjustPivotCenterAll(MenuCommand command)
{
AdjustPivot(command, xAxis: PivotAlignment.Mid, yAxis: PivotAlignment.Mid, zAxis: PivotAlignment.Mid);
}
[MenuItem("GameObject/PivotAdjuster/Center Top", false, 30001)]
static void AdjustPivotCenterTop(MenuCommand command)
{
AdjustPivot(command, xAxis: PivotAlignment.Mid, yAxis: PivotAlignment.Max, zAxis: PivotAlignment.Mid);
}
[MenuItem("GameObject/PivotAdjuster/Center Bottom", false, 30001)]
static void AdjustPivotCenterBottom(MenuCommand command)
{
AdjustPivot(command, xAxis: PivotAlignment.Mid, yAxis: PivotAlignment.Min, zAxis: PivotAlignment.Mid);
}
#region X-MIN
#region Y-MIN Z-MIN to Z-MAX
[MenuItem("GameObject/PivotAdjuster/X-Min/Y-Min/Z-Min", false, 30001)]
static void AdjustPivotXMinYMinZMin(MenuCommand command)
{
AdjustPivot(command, xAxis: PivotAlignment.Min, yAxis: PivotAlignment.Min, zAxis: PivotAlignment.Min);
}
[MenuItem("GameObject/PivotAdjuster/X-Min/Y-Min/Z-Mid", false, 30001)]
static void AdjustPivotXMinYMinZMid(MenuCommand command)
{
AdjustPivot(command, xAxis: PivotAlignment.Min, yAxis: PivotAlignment.Min, zAxis: PivotAlignment.Mid);
}
[MenuItem("GameObject/PivotAdjuster/X-Min/Y-Min/Z-Max", false, 30001)]
static void AdjustPivotXMinYMinZMax(MenuCommand command)
{
AdjustPivot(command, xAxis: PivotAlignment.Min, yAxis: PivotAlignment.Min, zAxis: PivotAlignment.Max);
}
#endregion
#region Y-MID Z-MIN to Z-MAX
[MenuItem("GameObject/PivotAdjuster/X-Min/Y-Mid/Z-Min", false, 30001)]
static void AdjustPivotXMinYMidZMin(MenuCommand command)
{
AdjustPivot(command, xAxis: PivotAlignment.Min, yAxis: PivotAlignment.Mid, zAxis: PivotAlignment.Min);
}
[MenuItem("GameObject/PivotAdjuster/X-Min/Y-Mid/Z-Mid", false, 30001)]
static void AdjustPivotXMinYMidZMid(MenuCommand command)
{
AdjustPivot(command, xAxis: PivotAlignment.Min, yAxis: PivotAlignment.Mid, zAxis: PivotAlignment.Mid);
}
[MenuItem("GameObject/PivotAdjuster/X-Min/Y-Mid/Z-Max", false, 30001)]
static void AdjustPivotXMinYMidZMax(MenuCommand command)
{
AdjustPivot(command, xAxis: PivotAlignment.Min, yAxis: PivotAlignment.Mid, zAxis: PivotAlignment.Max);
}
#endregion
#region Y-MAX Z-MIN to Z-MAX
[MenuItem("GameObject/PivotAdjuster/X-Min/Y-Max/Z-Min", false, 30001)]
static void AdjustPivotXMinYMaxZMin(MenuCommand command)
{
AdjustPivot(command, xAxis: PivotAlignment.Min, yAxis: PivotAlignment.Max, zAxis: PivotAlignment.Min);
}
[MenuItem("GameObject/PivotAdjuster/X-Min/Y-Max/Z-Mid", false, 30001)]
static void AdjustPivotXMinYMaxZMid(MenuCommand command)
{
AdjustPivot(command, xAxis: PivotAlignment.Min, yAxis: PivotAlignment.Max, zAxis: PivotAlignment.Mid);
}
[MenuItem("GameObject/PivotAdjuster/X-Min/Y-Max/Z-Max", false, 30001)]
static void AdjustPivotXMinYMaxZMax(MenuCommand command)
{
AdjustPivot(command, xAxis: PivotAlignment.Min, yAxis: PivotAlignment.Max, zAxis: PivotAlignment.Max);
}
#endregion
#endregion
#region X-MID
#region Y-MIN Z-MIN to Z-MAX
[MenuItem("GameObject/PivotAdjuster/X-Mid/Y-Min/Z-Min", false, 30001)]
static void AdjustPivotXMidYMinZMin(MenuCommand command)
{
AdjustPivot(command, xAxis: PivotAlignment.Mid, yAxis: PivotAlignment.Min, zAxis: PivotAlignment.Min);
}
[MenuItem("GameObject/PivotAdjuster/X-Mid/Y-Min/Z-Mid", false, 30001)]
static void AdjustPivotXMidYMinZMid(MenuCommand command)
{
AdjustPivot(command, xAxis: PivotAlignment.Mid, yAxis: PivotAlignment.Min, zAxis: PivotAlignment.Mid);
}
[MenuItem("GameObject/PivotAdjuster/X-Mid/Y-Min/Z-Max", false, 30001)]
static void AdjustPivotXMidYMinZMax(MenuCommand command)
{
AdjustPivot(command, xAxis: PivotAlignment.Mid, yAxis: PivotAlignment.Min, zAxis: PivotAlignment.Max);
}
#endregion
#region Y-MID Z-MIN to Z-MAX
[MenuItem("GameObject/PivotAdjuster/X-Mid/Y-Mid/Z-Min", false, 30001)]
static void AdjustPivotXMidYMidZMin(MenuCommand command)
{
AdjustPivot(command, xAxis: PivotAlignment.Mid, yAxis: PivotAlignment.Mid, zAxis: PivotAlignment.Min);
}
[MenuItem("GameObject/PivotAdjuster/X-Mid/Y-Mid/Z-Mid", false, 30001)]
static void AdjustPivotXMidYMidZMid(MenuCommand command)
{
AdjustPivot(command, xAxis: PivotAlignment.Mid, yAxis: PivotAlignment.Mid, zAxis: PivotAlignment.Mid);
}
[MenuItem("GameObject/PivotAdjuster/X-Mid/Y-Mid/Z-Max", false, 30001)]
static void AdjustPivotXMidYMidZMax(MenuCommand command)
{
AdjustPivot(command, xAxis: PivotAlignment.Mid, yAxis: PivotAlignment.Mid, zAxis: PivotAlignment.Max);
}
#endregion
#region Y-MAX Z-MIN to Z-MAX
[MenuItem("GameObject/PivotAdjuster/X-Mid/Y-Max/Z-Min", false, 30001)]
static void AdjustPivotXMidYMaxZMin(MenuCommand command)
{
AdjustPivot(command, xAxis: PivotAlignment.Mid, yAxis: PivotAlignment.Max, zAxis: PivotAlignment.Min);
}
[MenuItem("GameObject/PivotAdjuster/X-Mid/Y-Max/Z-Mid", false, 30001)]
static void AdjustPivotXMidYMaxZMid(MenuCommand command)
{
AdjustPivot(command, xAxis: PivotAlignment.Mid, yAxis: PivotAlignment.Max, zAxis: PivotAlignment.Mid);
}
[MenuItem("GameObject/PivotAdjuster/X-Mid/Y-Max/Z-Max", false, 30001)]
static void AdjustPivotXMidYMaxZMax(MenuCommand command)
{
AdjustPivot(command, xAxis: PivotAlignment.Mid, yAxis: PivotAlignment.Max, zAxis: PivotAlignment.Max);
}
#endregion
#endregion
#region X-MAX
#region Y-MIN Z-MIN to Z-MAX
[MenuItem("GameObject/PivotAdjuster/X-Max/Y-Min/Z-Min", false, 30001)]
static void AdjustPivotXMaxYMinZMin(MenuCommand command)
{
AdjustPivot(command, xAxis: PivotAlignment.Max, yAxis: PivotAlignment.Min, zAxis: PivotAlignment.Min);
}
[MenuItem("GameObject/PivotAdjuster/X-Max/Y-Min/Z-Mid", false, 30001)]
static void AdjustPivotXMaxYMinZMid(MenuCommand command)
{
AdjustPivot(command, xAxis: PivotAlignment.Max, yAxis: PivotAlignment.Min, zAxis: PivotAlignment.Mid);
}
[MenuItem("GameObject/PivotAdjuster/X-Max/Y-Min/Z-Max", false, 30001)]
static void AdjustPivotXMaxYMinZMax(MenuCommand command)
{
AdjustPivot(command, xAxis: PivotAlignment.Max, yAxis: PivotAlignment.Min, zAxis: PivotAlignment.Max);
}
#endregion
#region Y-MID Z-MIN to Z-MAX
[MenuItem("GameObject/PivotAdjuster/X-Max/Y-Mid/Z-Min", false, 30001)]
static void AdjustPivotXMaxYMidZMin(MenuCommand command)
{
AdjustPivot(command, xAxis: PivotAlignment.Max, yAxis: PivotAlignment.Mid, zAxis: PivotAlignment.Min);
}
[MenuItem("GameObject/PivotAdjuster/X-Max/Y-Mid/Z-Mid", false, 30001)]
static void AdjustPivotXMaxYMidZMid(MenuCommand command)
{
AdjustPivot(command, xAxis: PivotAlignment.Max, yAxis: PivotAlignment.Mid, zAxis: PivotAlignment.Mid);
}
[MenuItem("GameObject/PivotAdjuster/X-Max/Y-Mid/Z-Max", false, 30001)]
static void AdjustPivotXMaxYMidZMax(MenuCommand command)
{
AdjustPivot(command, xAxis: PivotAlignment.Max, yAxis: PivotAlignment.Mid, zAxis: PivotAlignment.Max);
}
#endregion
#region Y-MAX Z-MIN to Z-MAX
[MenuItem("GameObject/PivotAdjuster/X-Max/Y-Max/Z-Min", false, 30001)]
static void AdjustPivotXMaxYMaxZMin(MenuCommand command)
{
AdjustPivot(command, xAxis: PivotAlignment.Max, yAxis: PivotAlignment.Max, zAxis: PivotAlignment.Min);
}
[MenuItem("GameObject/PivotAdjuster/X-Max/Y-Max/Z-Mid", false, 30001)]
static void AdjustPivotXMaxYMaxZMid(MenuCommand command)
{
AdjustPivot(command, xAxis: PivotAlignment.Max, yAxis: PivotAlignment.Max, zAxis: PivotAlignment.Mid);
}
[MenuItem("GameObject/PivotAdjuster/X-Max/Y-Max/Z-Max", false, 30001)]
static void AdjustPivotXMaxYMaxZMax(MenuCommand command)
{
AdjustPivot(command, xAxis: PivotAlignment.Max, yAxis: PivotAlignment.Max, zAxis: PivotAlignment.Max);
}
#endregion
#endregion
#endregion
}
#endif
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment