-
https://create.roblox.com/store/asset/89367854250100/eye-of-horus
EYE_of_Horus(Plugin).rbxm (13.6 KB)
-
It catches minute errors in parts, and if you click the model while AutoEdit is turned on, it corrects minute errors in increments of 0.0625. (1/16)
-
It is good for people who use units of 0.5, 0.25, and 0.125.
-
You can modify the attached file to use it as your own plugin.
local toolbar = plugin:CreateToolbar("eye of horus")
local button = toolbar:CreateButton("eye of horus", "UI is on", "rbxassetid://12128496201")
local selectionService = game:GetService("Selection")
local CoreGui = game:GetService("CoreGui")
local screenGui = script.ScreenGui
screenGui.Name = "PluginUI"
screenGui.Parent = CoreGui
local uiVisible = true
-- UI toggle
button.Click:Connect(function()
uiVisible = not uiVisible
screenGui.Enabled = uiVisible
end)
local Autoedit = false
local Autoedit_Button = screenGui.Frame.Autoedit
local Display_Mode = true
local Display_Mode_Button = screenGui.Frame.Display_Mode
local Display_Folder = CoreGui:FindFirstChild("EyeOfHorusDisplay")
if not Display_Folder then
Display_Folder = Instance.new("Folder")
Display_Folder.Name = "EyeOfHorusDisplay"
Display_Folder.Parent = CoreGui
end
-- Autoedit reset and toggle
local function updateAutoeditButton()
if Autoedit then
Autoedit_Button.Text = "Autoedit: ON"
Autoedit_Button.TextColor3 = Color3.fromRGB(0, 255, 0)
else
Autoedit_Button.Text = "Autoedit: OFF"
Autoedit_Button.TextColor3 = Color3.fromRGB(255, 0, 0)
end
end
updateAutoeditButton()
local function updateDisplay_ModeButton()
if Display_Mode then
Display_Mode_Button.Text = "Display Mode: BillboardGui"
else
Display_Mode_Button.Text = "Display Mode: Highlight"
end
end
updateDisplay_ModeButton()
--SNAP
local SNAP_STEP = 0.0625
local function snapNumber(num)
return math.round(num / SNAP_STEP) * SNAP_STEP
end
local function snapVector3(vec)
return Vector3.new(
snapNumber(vec.X),
snapNumber(vec.Y),
snapNumber(vec.Z)
)
end
-- tables
local connections = {}
local activeHighlights = {}
local updateTimers = {}
local isCorrecting = false
-- reset highlights
local function clearAllHighlights()
for obj, hl in pairs(activeHighlights) do
if hl and hl.Parent then
hl:Destroy()
end
end
table.clear(activeHighlights)
end
local function updateHighlight(obj, hasError)
if hasError then
if not activeHighlights[obj] then
if Display_Mode then
local box = Instance.new("BoxHandleAdornment")
box.Name = "ErrorVisual"
box.Size = obj.Size + Vector3.new(0.05, 0.05, 0.05) -- 원본보다 약간 크게 (겹침 방지)
box.Adornee = obj -- 기준이 될 파츠
box.AlwaysOnTop = true -- 벽 너머로도 보이게 설정
box.ZIndex = 10
box.Transparency = 0.5 -- ForceField 느낌을 위해 반투명 설정
box.Color3 = Color3.fromRGB(255, 0, 0)
box.Parent = Display_Folder -- Display_Folder가 CoreGui 안에 있어도 됨
local bill = Instance.new("BillboardGui")
bill.Size = UDim2.new(0,50,0,50)
bill.AlwaysOnTop = true
bill.MaxDistance = 1000000
bill.Adornee = obj -- 파츠에 직접 붙임
bill.Parent = box
local image = Instance.new("ImageLabel")
image.Size = UDim2.new(1,0,1,0)
image.BackgroundTransparency = 1
image.Image = "rbxassetid://13517960032"
image.Parent = bill
activeHighlights[obj] = box
else
local hl = Instance.new("Highlight")
hl.Adornee = obj
hl.FillColor = Color3.fromRGB(255, 0, 0)
hl.OutlineColor = Color3.fromRGB(255, 0, 0)
hl.DepthMode = Enum.HighlightDepthMode.AlwaysOnTop
hl.Name = "ErrorHighlight"
hl.Parent = Display_Folder
activeHighlights[obj] = hl
end
end
else
if activeHighlights[obj] then
activeHighlights[obj]:Destroy()
activeHighlights[obj] = nil
end
end
end
local function updateUI(obj)
if not obj or not obj.Parent then return end
screenGui.Frame.PX.Text = obj.Position.X
screenGui.Frame.PY.Text = obj.Position.Y
screenGui.Frame.PZ.Text = obj.Position.Z
screenGui.Frame.OX.Text = obj.Rotation.X
screenGui.Frame.OY.Text = obj.Rotation.Y
screenGui.Frame.OZ.Text = obj.Rotation.Z
screenGui.Frame.SX.Text = obj.Size.X
screenGui.Frame.SY.Text = obj.Size.Y
screenGui.Frame.SZ.Text = obj.Size.Z
if obj.CanCollide == true then
screenGui.Frame.cancollide.Text = "CanCollide = true"
screenGui.Frame.cancollide.TextColor3 = Color3.fromRGB(0, 255, 0)
else
screenGui.Frame.cancollide.Text = "CanCollide = false"
screenGui.Frame.cancollide.TextColor3 = Color3.fromRGB(255, 0, 0)
end
if obj.CanTouch == true then
screenGui.Frame.cantouch.Text = "CanTouch = true"
screenGui.Frame.cantouch.TextColor3 = Color3.fromRGB(0, 255, 0)
else
screenGui.Frame.cantouch.Text = "CanTouch = false"
screenGui.Frame.cantouch.TextColor3 = Color3.fromRGB(255, 0, 0)
end
if obj.Massless == true then
screenGui.Frame.massless.Text = "Massless = true"
screenGui.Frame.massless.TextColor3 = Color3.fromRGB(0, 255, 0)
else
screenGui.Frame.massless.Text = "Massless = false"
screenGui.Frame.massless.TextColor3 = Color3.fromRGB(255, 0, 0)
end
if obj.Anchored == true then
screenGui.Frame.anchor.Text = "Anchored = true"
screenGui.Frame.anchor.TextColor3 = Color3.fromRGB(0, 255, 0)
else
screenGui.Frame.anchor.Text = "Anchored = false"
screenGui.Frame.anchor.TextColor3 = Color3.fromRGB(255, 0, 0)
end
if obj:IsA("MeshPart") then
screenGui.Frame.CollisionFidelity.Text = "CollisionFidelity = " .. obj.CollisionFidelity.Name
end
end
----------------------------------
local inspectionQueue = {}
local isProcessingQueue = false
local MAX_PARTS_PER_FRAME = 25
local function performCheckAndFix(obj, canFix)
if isCorrecting then return end
local hasError = false
-- 1. Position/Size
for _, prop in ipairs({"Position", "Size"}) do
local currentVal = obj[prop]
local snappedVal = snapVector3(currentVal)
if (currentVal - snappedVal).Magnitude > 0.000001 then
if Autoedit and canFix then
isCorrecting = true
obj[prop] = snappedVal
isCorrecting = false
else
hasError = true
end
end
end
-- 2. Rotation
local currentRot = obj.Rotation
local snappedRot = snapVector3(currentRot)
if (currentRot - snappedRot).Magnitude > 0.01 then
if Autoedit and canFix then
isCorrecting = true
obj.Rotation = snappedRot
isCorrecting = false
else
hasError = true
end
end
updateHighlight(obj, hasError)
end
local function processInspectionQueue()
if isProcessingQueue then return end
isProcessingQueue = true
while #inspectionQueue > 0 do
local batchCount = 0
while #inspectionQueue > 0 and batchCount < MAX_PARTS_PER_FRAME do
local taskData = table.remove(inspectionQueue, 1)
local obj = taskData.obj
if obj and obj.Parent then
if screenGui.Enabled then
updateUI(obj)
end
performCheckAndFix(obj, taskData.canFix)
end
batchCount += 1
end
task.wait()
end
isProcessingQueue = false
end
local function checkAndFixErrors(obj, canFix)
table.insert(inspectionQueue, {obj = obj, canFix = canFix})
processInspectionQueue()
end
-- Autoedit
Autoedit_Button.MouseButton1Click:Connect(function()
Autoedit = not Autoedit
updateAutoeditButton()
end)
Display_Mode_Button.MouseButton1Click:Connect(function()
Display_Mode = not Display_Mode
updateDisplay_ModeButton()
end)
-- main logic
selectionService.SelectionChanged:Connect(function()
-- 1. 초기화
for _, conn in ipairs(connections) do conn:Disconnect() end
table.clear(connections)
table.clear(inspectionQueue)
clearAllHighlights()
local selectedObjects = selectionService:Get()
if #selectedObjects == 0 then return end
local partsToInspect = {}
local canFixMap = {}
-- 2. 대상 수집
for _, root in ipairs(selectedObjects) do
if root:IsA("BasePart") then
table.insert(partsToInspect, root)
canFixMap[root] = true
end
for _, descendant in ipairs(root:GetDescendants()) do
if descendant:IsA("BasePart") then
if not canFixMap[descendant] then
table.insert(partsToInspect, descendant)
canFixMap[descendant] = true
end
end
end
end
-- 3. 대기열에 삽입 및 이벤트 연결
for _, part in ipairs(partsToInspect) do
table.insert(inspectionQueue, {obj = part, canFix = canFixMap[part]})
local conn = part.Changed:Connect(function(prop)
if prop == "Position" or prop == "Rotation" or prop == "Size" then
checkAndFixErrors(part, canFixMap[part])
end
end)
table.insert(connections, conn)
end
-- 4. 초기 스캔 실행
processInspectionQueue()
end)
