I am making a rotation tool in a building game I’m making, but I cannot get it to work right. Every solution I tried either stops it from rotating completely, or it jitters like crazy.
I tried:
- Snapping rotation to
ROTATION_INCREMENT(I needed to anyways) but that still causes it to jitter. - Adding a
cumulativeAnglestable to track X/Y/Z rotation.
Rotate Script:
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local UserInputService = game:GetService("UserInputService")
local CollectionService = game:GetService("CollectionService")
local Workspace = game:GetService("Workspace")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Remotes = ReplicatedStorage:WaitForChild("Remotes")
local ValidateMove = Remotes:WaitForChild("ValidateMove")
local placedBlocks = workspace:WaitForChild("PlacedBlocks")
local player = Players.LocalPlayer
local pgui = player:WaitForChild("PlayerGui")
local mouse = player:GetMouse()
local selectionBox = script.SelectionBox
local handles = script.RotateHandles
local hoverBox = script.SelectionBoxHover
local playerModule = player.PlayerScripts.PlayerModule
local CameraInput = require(playerModule.CameraModule.CameraInput)
local ControlModule = require(playerModule.ControlModule)
local module = {}
local enabled = false
local dragging = false
local currentHandles
local boundingPart
local originalBoundingCFrame
local modelOffsets = {}
local selectedModels = {}
local renderConn, mouseDownConn
local handleMouseDownConn, handleMouseUpConn, handleMouseDragConn
local shiftDownIgnoreArrowsFlag = false
local shiftConnBegan, shiftConnEnded
local DragConnection, guiDragConn, guiDragEndConn
local Corner1 = Vector2.zero
local validateDebounce = false
local ScaleGui = script.MoveGui
local GuiStarted = false
local lastCFrame
local lastTime = tick()
local cumulativeAngles = {X = 0, Y = 0, Z = 0}
local modelOffsets = {}
local movePresets = {
90,
45,
15,
5,
1,
}
local ROTATE_INCREMENT = movePresets[2]
local modelSelectionBoxes = {}
local moveSelection = script.Selection_Rotate
local moveSelectionFrame = moveSelection:WaitForChild("Frame")
local moveLastValidText = ROTATE_INCREMENT
local typeMoveConn
local setMoveConn
local moveMinValue = 0.001
local moveMaxValue = 2
local function AngleFromAxis(axis, angle)
local snapped = math.floor(angle / ROTATE_INCREMENT + 0.5) * ROTATE_INCREMENT
local rotX, rotY, rotZ = 0, 0, 0
if axis == Enum.Axis.X then
rotX = math.rad(snapped)
elseif axis == Enum.Axis.Y then
rotY = math.rad(snapped)
elseif axis == Enum.Axis.Z then
rotZ = math.rad(snapped)
end
return {rotX, rotY, rotZ}
end
local function getScaledAmount(model, selectedModels, useVector, SetSize)
if not model or not selectedModels then return 1 end
local BlockId = model:GetAttribute("BLOCKID")
if not BlockId then return 1 end
local amount = 0
for _, block in ipairs(selectedModels) do
if block:GetAttribute("BLOCKID") == BlockId then
local part = block:FindFirstChildWhichIsA("BasePart")
if part then
local size = part.Size
if useVector and SetSize then
if block == model then
size = SetSize
end
end
local blocksX = size.X / 2
local blocksY = size.Y / 2
local blocksZ = size.Z / 2
amount += math.max(1, math.round(blocksX * blocksY * blocksZ))
end
end
end
return amount
end
local function formatSize(num)
local rounded = math.floor(num * 1000 + 0.5) / 1000
local str = string.format("%.3f", rounded)
str = str:gsub("%.?0+$", "")
return str
end
local function isValidNumber(text, minValue, maxValue)
if not string.match(text, "^%d*%.?%d%d?%d?$") then
return false
end
local num = tonumber(text)
if not num then
return false
end
return num >= minValue and num <= maxValue
end
local function getBiggestNumberFromSize(vec)
local biggest = vec.X
if vec.Y > biggest then biggest = vec.Y end
if vec.Z > biggest then biggest = vec.Z end
return biggest
end
local function initGui()
if GuiStarted then return end
GuiStarted = true
if typeMoveConn then
typeMoveConn:Disconnect()
typeMoveConn = nil
end
if setMoveConn then
setMoveConn:Disconnect()
setMoveConn = nil
end
local frame = ScaleGui:FindFirstChild("Frame")
if not frame then return end
local increment = frame:FindFirstChild("Increment")
if not increment then return end
local MoveTextBox = increment:FindFirstChild("TextBox")
if not MoveTextBox then return end
local incrementButton = increment:FindFirstChild("Button")
if not incrementButton then return end
local function updateButtons()
MoveTextBox.Text = ROTATE_INCREMENT
end
updateButtons()
typeMoveConn = MoveTextBox.FocusLost:Connect(function()
local text = MoveTextBox.Text
if text == "" then
MoveTextBox.Text = moveLastValidText
return
end
if isValidNumber(text, moveMinValue, moveMaxValue) then
moveLastValidText = text
ROTATE_INCREMENT = tonumber(text)
else
MoveTextBox.Text = moveLastValidText
ROTATE_INCREMENT = tonumber(moveLastValidText)
end
end)
setMoveConn = incrementButton.MouseButton1Click:Connect(function()
local defaultMoveIndex = 0
for i, v in ipairs(movePresets) do
if v == ROTATE_INCREMENT then
defaultMoveIndex = i
break
end
end
defaultMoveIndex += 1
if defaultMoveIndex > #movePresets then
defaultMoveIndex = 1
end
ROTATE_INCREMENT = movePresets[defaultMoveIndex]
MoveTextBox.Text = ROTATE_INCREMENT
end)
end
local function updateGui(textOnly)
if #selectedModels > 0 then
ScaleGui.Parent = pgui
local frame = ScaleGui:FindFirstChild("Frame")
if not frame then return end
local slot = frame:FindFirstChild("Slot")
if not slot then return end
local viewport = slot:FindFirstChild("BlockIcon")
if not viewport then return end
local amountText = slot:FindFirstChild("Amount")
if not amountText then return end
if not textOnly then
for _, v in pairs(viewport:GetChildren()) do
if v:IsA("Model") then
v:Destroy()
end
end
local firstName = selectedModels[1].Name
local allSame = true
for _, model in ipairs(selectedModels) do
if model.Name ~= firstName then
allSame = false
break
end
end
local viewportBlock
if allSame then
local replicatedBlock = game.ReplicatedStorage:WaitForChild("Blocks"):FindFirstChild(firstName)
if replicatedBlock then
viewportBlock = replicatedBlock:Clone()
end
else
local templateBlock = game.ReplicatedStorage:WaitForChild("TemplateBlock")
viewportBlock = templateBlock:Clone()
end
if viewportBlock then
local orientation, size = viewportBlock:GetBoundingBox()
local cameraDistance = getBiggestNumberFromSize(size) * 1.5
viewportBlock:PivotTo(CFrame.new(0,0,cameraDistance) * CFrame.Angles(0, math.rad(45), math.rad(45)))
viewportBlock.Parent = viewport
end
end
local userFolder = placedBlocks:FindFirstChild(player.UserId)
if userFolder then
local countedBlocks = {}
local totalAmount = 0
for _, model in ipairs(selectedModels) do
local blockName = model.Name
if not countedBlocks[blockName] then
local amt = getScaledAmount(model, selectedModels)
countedBlocks[blockName] = amt
totalAmount += amt
end
end
amountText.Text = tostring(totalAmount)
end
else
ScaleGui.Parent = script
end
end
local function clearSelection()
if validateDebounce then return end
selectedModels = {}
if boundingPart then
boundingPart:Destroy()
boundingPart = nil
end
if handleMouseDownConn then handleMouseDownConn:Disconnect() handleMouseDownConn = nil end
if handleMouseUpConn then handleMouseUpConn:Disconnect() handleMouseUpConn = nil end
if handleMouseDragConn then handleMouseDragConn:Disconnect() handleMouseDragConn = nil end
if currentHandles then
currentHandles:Destroy()
currentHandles = nil
end
selectionBox.Parent = script
selectionBox.Adornee = nil
hoverBox.Parent = script
hoverBox.Adornee = nil
for model, box in pairs(modelSelectionBoxes) do
if box then
box:Destroy()
end
end
modelSelectionBoxes = {}
end
local function getBoundingBox(models)
if #models == 0 then return nil, nil end
local minVec, maxVec
for _, model in ipairs(models) do
for _, part in ipairs(model:GetDescendants()) do
if part:IsA("BasePart") then
local cf = part.CFrame
local size = part.Size / 2
local corners = {
cf * Vector3.new(-size.X,-size.Y,-size.Z),
cf * Vector3.new(-size.X,-size.Y, size.Z),
cf * Vector3.new(-size.X, size.Y,-size.Z),
cf * Vector3.new(-size.X, size.Y, size.Z),
cf * Vector3.new( size.X,-size.Y,-size.Z),
cf * Vector3.new( size.X,-size.Y, size.Z),
cf * Vector3.new( size.X, size.Y,-size.Z),
cf * Vector3.new( size.X, size.Y, size.Z),
}
for _, corner in ipairs(corners) do
if not minVec then
minVec = corner
maxVec = corner
else
minVec = Vector3.new(math.min(minVec.X, corner.X), math.min(minVec.Y, corner.Y), math.min(minVec.Z, corner.Z))
maxVec = Vector3.new(math.max(maxVec.X, corner.X), math.max(maxVec.Y, corner.Y), math.max(maxVec.Z, corner.Z))
end
end
end
end
end
if not minVec or not maxVec then return nil, nil end
return CFrame.new((minVec + maxVec) / 2), maxVec - minVec
end
local function updateBoundingPart()
if #selectedModels == 0 then
if boundingPart then boundingPart:Destroy() end
boundingPart = nil
selectionBox.Adornee = nil
if currentHandles then
handleMouseDownConn:Disconnect() handleMouseDownConn = nil
handleMouseUpConn:Disconnect() handleMouseUpConn = nil
handleMouseDragConn:Disconnect() handleMouseDragConn = nil
currentHandles:Destroy()
currentHandles = nil
end
return
end
local cf, size = getBoundingBox(selectedModels)
if not cf or not size then return end
if not boundingPart then
boundingPart = Instance.new("Part")
boundingPart.CanQuery = false
boundingPart.Anchored = true
boundingPart.Transparency = 1
boundingPart.CanCollide = false
boundingPart.Name = "MoveBoundingPart"
boundingPart.Parent = workspace
selectionBox.Parent = workspace
selectionBox.Adornee = boundingPart
end
boundingPart.CFrame = cf
boundingPart.Size = size
end
local function toggleSelection(model, shift, touchEnabled)
if validateDebounce then return end
if #selectedModels > 1 and not shift and not touchEnabled then
clearSelection()
toggleSelection(model)
return
end
local idx = table.find(selectedModels, model)
if idx then
table.remove(selectedModels, idx)
if modelSelectionBoxes[model] then
modelSelectionBoxes[model]:Destroy()
modelSelectionBoxes[model] = nil
end
else
table.insert(selectedModels, model)
local box = selectionBox:Clone()
box.Adornee = model
box.Parent = workspace
modelSelectionBoxes[model] = box
end
updateBoundingPart()
updateGui()
end
local function beginRotate(axis)
if not boundingPart or validateDebounce or shiftDownIgnoreArrowsFlag then return end
dragging = true
originalBoundingCFrame = boundingPart.CFrame
local pivot = originalBoundingCFrame.Position
modelOffsets = {}
for _, model in ipairs(selectedModels) do
if model.PrimaryPart then
local relPos = model.PrimaryPart.Position - pivot
local relCFrame = CFrame.new(relPos):ToObjectSpace(originalBoundingCFrame:inverse() * model.PrimaryPart.CFrame)
modelOffsets[model] = {PositionOffset = relPos, OrientationOffset = model.PrimaryPart.CFrame - CFrame.new(model.PrimaryPart.Position)}
end
end
cumulativeAngles = {X=0, Y=0, Z=0}
end
local function endRotate()
if not boundingPart or not dragging then return end
dragging = false
validateDebounce = true
local rotations = {}
for model, offset in pairs(modelOffsets) do
if model.PrimaryPart then
table.insert(rotations, {Model = model, CFrame = boundingPart.CFrame * offset})
end
end
local result = ValidateMove:InvokeServer(rotations)
if not result or not result.Success then
for model, offset in pairs(modelOffsets) do
if model.PrimaryPart then
model:SetPrimaryPartCFrame(originalBoundingCFrame * offset)
end
end
boundingPart.CFrame = originalBoundingCFrame
script.Reject:Play()
end
modelOffsets = {}
validateDebounce = false
end
local function rotateBounding(axis, relativeAngle, deltaRadius)
if not boundingPart or not dragging or validateDebounce then return end
if axis == Enum.Axis.X then
cumulativeAngles.X = cumulativeAngles.X + relativeAngle
elseif axis == Enum.Axis.Y then
cumulativeAngles.Y = cumulativeAngles.Y + relativeAngle
elseif axis == Enum.Axis.Z then
cumulativeAngles.Z = cumulativeAngles.Z + relativeAngle
else
return
end
local snapX = math.floor(cumulativeAngles.X / ROTATE_INCREMENT + 0.5) * ROTATE_INCREMENT
local snapY = math.floor(cumulativeAngles.Y / ROTATE_INCREMENT + 0.5) * ROTATE_INCREMENT
local snapZ = math.floor(cumulativeAngles.Z / ROTATE_INCREMENT + 0.5) * ROTATE_INCREMENT
local pivot = originalBoundingCFrame.Position
local rotationCFrame = CFrame.Angles(math.rad(snapX), math.rad(snapY), math.rad(snapZ))
local newCFrame = CFrame.new(pivot) * rotationCFrame
boundingPart.CFrame = newCFrame
for model, offset in pairs(modelOffsets) do
if model.PrimaryPart then
model:SetPrimaryPartCFrame(newCFrame * offset)
end
end
end
local function updateHover()
if UserInputService.TouchEnabled then return end
if not mouse.Target then
hoverBox.Adornee = nil
return
end
local target = mouse.Target
local model = target:FindFirstAncestorOfClass("Model")
if model and CollectionService:HasTag(model, "Scalable") then
if model:IsDescendantOf(workspace.PlacedBlocks:FindFirstChild(player.UserId)) then
hoverBox.Parent = workspace
hoverBox.Adornee = model.PrimaryPart or target
else
hoverBox.Adornee = nil
end
else
hoverBox.Adornee = nil
end
end
local function updateHandlesParent()
if currentHandles then
if shiftDownIgnoreArrowsFlag then
currentHandles.Parent = workspace
else
currentHandles.Parent = pgui
end
end
end
local function onClick()
if validateDebounce then return end
if not mouse.Target then return end
local model = mouse.Target:FindFirstAncestorOfClass("Model")
if not model or not CollectionService:HasTag(model, "Scalable") then return end
if UserInputService:IsKeyDown(Enum.KeyCode.LeftShift) or UserInputService:IsKeyDown(Enum.KeyCode.RightShift) or UserInputService:IsKeyDown(Enum.KeyCode.LeftControl) or UserInputService:IsKeyDown(Enum.KeyCode.RightControl) or UserInputService.TouchEnabled then
toggleSelection(model, true, UserInputService.TouchEnabled)
else
if not table.find(selectedModels, model) then
clearSelection()
end
toggleSelection(model, false, UserInputService.TouchEnabled)
end
if boundingPart then
if currentHandles then
handleMouseDownConn:Disconnect() handleMouseDownConn = nil
handleMouseUpConn:Disconnect() handleMouseUpConn = nil
handleMouseDragConn:Disconnect() handleMouseDragConn = nil
currentHandles:Destroy()
currentHandles = nil
end
currentHandles = handles:Clone()
currentHandles.Adornee = boundingPart
updateHandlesParent()
handleMouseDownConn = currentHandles.MouseButton1Down:Connect(function(axis)
beginRotate(axis)
end)
handleMouseDragConn = currentHandles.MouseDrag:Connect(function(axis, relativeAngle, deltaRadius)
rotateBounding(axis, relativeAngle, deltaRadius)
end)
handleMouseUpConn = currentHandles.MouseButton1Up:Connect(endRotate)
end
end
function module.Enable()
if enabled then return end
enabled = true
if renderConn then renderConn:Disconnect() renderConn = nil end
if mouseDownConn then mouseDownConn:Disconnect() mouseDownConn = nil end
renderConn = RunService.RenderStepped:Connect(updateHover)
mouseDownConn = mouse.Button1Down:Connect(onClick)
shiftConnBegan = UserInputService.InputBegan:Connect(function(input)
if input.UserInputType == Enum.UserInputType.Keyboard and (input.KeyCode == Enum.KeyCode.LeftShift or input.KeyCode == Enum.KeyCode.RightShift or input.KeyCode == Enum.KeyCode.LeftControl or input.KeyCode == Enum.KeyCode.RightControl) then
shiftDownIgnoreArrowsFlag = true
updateHandlesParent()
end
end)
shiftConnEnded = UserInputService.InputEnded:Connect(function(input)
if input.UserInputType == Enum.UserInputType.Keyboard and (input.KeyCode == Enum.KeyCode.LeftShift or input.KeyCode == Enum.KeyCode.RightShift or input.KeyCode == Enum.KeyCode.LeftControl or input.KeyCode == Enum.KeyCode.RightControl) then
shiftDownIgnoreArrowsFlag = false
updateHandlesParent()
end
end)
local MIN_DRAG_DISTANCE = 5
local DraggingActive = false
local Corner1 = Vector2.new()
local tempModelSelect = {}
guiDragConn = UserInputService.InputBegan:Connect(function(input, gameProcessed)
if input.UserInputType ~= Enum.UserInputType.MouseButton1 or gameProcessed then return end
Corner1 = Vector2.new(mouse.X, mouse.Y)
DraggingActive = false
if DragConnection then DragConnection:Disconnect() end
DragConnection = UserInputService.InputChanged:Connect(function(inputObject)
if inputObject.UserInputType ~= Enum.UserInputType.MouseMovement then return end
local currentPos = Vector2.new(mouse.X, mouse.Y)
local delta = (currentPos - Corner1).magnitude
if not DraggingActive and delta >= MIN_DRAG_DISTANCE then
DraggingActive = true
moveSelection.Parent = pgui
moveSelectionFrame.Position = UDim2.fromOffset(Corner1.X, Corner1.Y)
moveSelectionFrame.Size = UDim2.fromOffset(0, 0)
clearSelection()
end
if DraggingActive then
local size = currentPos - Corner1
local pos = Corner1 + Vector2.new(math.min(0, size.X), math.min(0, size.Y))
local absSize = Vector2.new(math.abs(size.X), math.abs(size.Y))
moveSelectionFrame.Position = UDim2.fromOffset(pos.X, pos.Y)
moveSelectionFrame.Size = UDim2.fromOffset(absSize.X, absSize.Y)
local userFolder = placedBlocks:FindFirstChild(player.UserId)
if not userFolder then return end
for _, model in ipairs(userFolder:GetChildren()) do
if model:IsA("Model") and CollectionService:HasTag(model, "Scalable") and model.PrimaryPart then
local screenPos, onScreen = workspace.CurrentCamera:WorldToViewportPoint(model.PrimaryPart.Position)
if onScreen then
local pos2D = Vector2.new(screenPos.X, screenPos.Y)
local inRect = pos2D.X >= pos.X and pos2D.X <= pos.X + absSize.X and
pos2D.Y >= pos.Y and pos2D.Y <= pos.Y + absSize.Y
if inRect then
if not modelSelectionBoxes[model] then
local box = selectionBox:Clone()
box.Adornee = model
box.Parent = workspace
modelSelectionBoxes[model] = box
end
else
if modelSelectionBoxes[model] then
modelSelectionBoxes[model]:Destroy()
modelSelectionBoxes[model] = nil
end
end
end
end
end
end
end)
end)
guiDragEndConn = UserInputService.InputEnded:Connect(function(input, gameProcessed)
if input.UserInputType ~= Enum.UserInputType.MouseButton1 then return end
moveSelection.Parent = script
if DragConnection then
DragConnection:Disconnect()
DragConnection = nil
end
if not DraggingActive then return end
DraggingActive = false
local corner2 = Vector2.new(mouse.X, mouse.Y)
local size = corner2 - Corner1
local negate = Vector2.new(size.X - math.abs(size.X), size.Y - math.abs(size.Y)) / 2
local adjustedCorner1 = Corner1 + negate
local adjustedCorner2 = corner2 - negate
local userFolder = placedBlocks:FindFirstChild(player.UserId)
if not userFolder then return end
local topLeft = Vector2.new(math.min(adjustedCorner1.X, adjustedCorner2.X), math.min(adjustedCorner1.Y, adjustedCorner2.Y))
local bottomRight = Vector2.new(math.max(adjustedCorner1.X, adjustedCorner2.X), math.max(adjustedCorner1.Y, adjustedCorner2.Y))
clearSelection()
local selectedTotal = 0
for _, model in ipairs(userFolder:GetChildren()) do
if model:IsA("Model") and CollectionService:HasTag(model, "Scalable") and model.PrimaryPart then
local minScreen, maxScreen
for _, part in ipairs(model:GetDescendants()) do
if part:IsA("BasePart") then
local corners = {
part.CFrame * Vector3.new(-part.Size.X/2, -part.Size.Y/2, -part.Size.Z/2),
part.CFrame * Vector3.new(-part.Size.X/2, -part.Size.Y/2, part.Size.Z/2),
part.CFrame * Vector3.new(-part.Size.X/2, part.Size.Y/2, -part.Size.Z/2),
part.CFrame * Vector3.new(-part.Size.X/2, part.Size.Y/2, part.Size.Z/2),
part.CFrame * Vector3.new(part.Size.X/2, -part.Size.Y/2, -part.Size.Z/2),
part.CFrame * Vector3.new(part.Size.X/2, -part.Size.Y/2, part.Size.Z/2),
part.CFrame * Vector3.new(part.Size.X/2, part.Size.Y/2, -part.Size.Z/2),
part.CFrame * Vector3.new(part.Size.X/2, part.Size.Y/2, part.Size.Z/2),
}
for _, corner in ipairs(corners) do
local screenPos, onScreen = workspace.CurrentCamera:WorldToViewportPoint(corner)
if onScreen then
local pos2D = Vector2.new(screenPos.X, screenPos.Y)
if not minScreen then
minScreen = pos2D
maxScreen = pos2D
else
minScreen = Vector2.new(math.min(minScreen.X, pos2D.X), math.min(minScreen.Y, pos2D.Y))
maxScreen = Vector2.new(math.max(maxScreen.X, pos2D.X), math.max(maxScreen.Y, pos2D.Y))
end
end
end
end
end
if minScreen and maxScreen then
if maxScreen.X >= topLeft.X and minScreen.X <= bottomRight.X and
maxScreen.Y >= topLeft.Y and minScreen.Y <= bottomRight.Y then
selectedTotal += 1
toggleSelection(model, true, UserInputService.TouchEnabled)
end
end
end
if selectedTotal <= 0 then
ScaleGui.Parent = script
end
tempModelSelect = {}
end
updateBoundingPart()
if boundingPart then
if currentHandles then
handleMouseDownConn:Disconnect()
handleMouseUpConn:Disconnect()
handleMouseDragConn:Disconnect()
currentHandles:Destroy()
currentHandles = nil
end
currentHandles = handles:Clone()
currentHandles.Adornee = boundingPart
updateHandlesParent()
handleMouseDownConn = currentHandles.MouseButton1Down:Connect(beginRotate)
handleMouseUpConn = currentHandles.MouseButton1Up:Connect(endRotate)
handleMouseDragConn = currentHandles.MouseDrag:Connect(rotateBounding)
end
end)
initGui()
end
function module.Disable()
if not enabled then return end
enabled = false
if renderConn then renderConn:Disconnect() renderConn = nil end
if mouseDownConn then mouseDownConn:Disconnect() mouseDownConn = nil end
if shiftConnBegan then shiftConnBegan:Disconnect() shiftConnBegan = nil end
if shiftConnEnded then shiftConnEnded:Disconnect() shiftConnEnded = nil end
if typeMoveConn then typeMoveConn:Disconnect() typeMoveConn = nil end
if setMoveConn then setMoveConn:Disconnect() setMoveConn = nil end
shiftDownIgnoreArrowsFlag = false
validateDebounce = false
dragging = false
if handleMouseDownConn then handleMouseDownConn:Disconnect() handleMouseDownConn = nil end
if handleMouseUpConn then handleMouseUpConn:Disconnect() handleMouseUpConn = nil end
if handleMouseDragConn then handleMouseDragConn:Disconnect() handleMouseDragConn = nil end
if guiDragConn then guiDragConn:Disconnect() guiDragConn = nil end
if guiDragEndConn then guiDragEndConn:Disconnect() guiDragEndConn = nil end
if DragConnection then DragConnection:Disconnect() DragConnection = nil end
moveSelection.Parent = script
ScaleGui.Parent = script
clearSelection()
if currentHandles then
currentHandles:Destroy()
currentHandles = nil
end
if boundingPart then
boundingPart:Destroy()
boundingPart = nil
end
selectedModels = {}
selectionBox.Parent = script
selectionBox.Adornee = nil
hoverBox.Parent = script
hoverBox.Adornee = nil
for model, box in pairs(modelSelectionBoxes) do
if box then
box:Destroy()
end
end
modelSelectionBoxes = {}
CameraInput.setInputEnabled(true)
ControlModule:Enable()
end
return module
Any help is appreciated since I don’t know any way to fix it myself