How do I make an increment with snapping for building system?

Hello! I’m working on my building system and I need help making an incredment that snaps like btools.

Here’s my building system vs what I want it to do.

My scripts.

HAMMER CLIENT:

-- funny variables
local rs = game.ReplicatedStorage
local run_service = game["Run Service"]
local uis = game.UserInputService

local player = game.Players.LocalPlayer
local tool = script.Parent
local camera = workspace.CurrentCamera
local remotes = tool.remotes
local pc = require(tool.placement_calculator)

local hammer_gui = script.hammer_gui
local build_button = hammer_gui.menu.build
local delete_button = hammer_gui.menu.delete
local block_menu = hammer_gui.block_menu

local building = true

local blocks = rs:WaitForChild("blocks")
local selected_block = blocks.plastic
local preview = nil
local last_selected_instance = nil -- Keep track of the last selected object

local equipped = false

-- make a ghost thing of a block to see it before placing it
function make_preview()
	preview = selected_block:Clone()
	preview.Parent = workspace
	preview.CanCollide = false
	preview.CanQuery = false
	preview.Transparency = math.min(preview.Transparency + 0.5, 0.7)

	for i, part in pairs(preview:GetDescendants()) do
		if part:IsA("BasePart") then
			-- Increase the transparency of each part by 50%
			part.Transparency = math.min(part.Transparency + 0.5, 0.7)
		end
	end
end


-- toggle for if the preview should be on or off
function preview_toggle()
	if equipped and not preview and building then
		make_preview()
	end

	if not equipped and preview then
		preview:Destroy()
		preview = nil
	end
end

-- render block preview
function render_preview()
	local mouse_location = uis:GetMouseLocation()
	local unit_ray = camera:ViewportPointToRay(mouse_location.X, mouse_location.Y)

	local params = RaycastParams.new()
	params.FilterDescendantsInstances = game.Players.LocalPlayer.Character:GetChildren()
	params.FilterType = Enum.RaycastFilterType.Exclude
	
	local raycast = workspace:Raycast(unit_ray.Origin, unit_ray.Direction * 5000, params)

	if raycast and preview and equipped and building then
		
		local normal_cf = CFrame.lookAt(raycast.Position, raycast.Position + raycast.Normal)
		local relative_snapped = normal_cf:PointToObjectSpace(raycast.Position)

		local x_vector = normal_cf:VectorToWorldSpace(Vector3.xAxis * -math.sign(relative_snapped.X))
		local y_vector = normal_cf:VectorToWorldSpace(Vector3.yAxis * -math.sign(relative_snapped.Y))

		local cf = CFrame.fromMatrix(raycast.Position, x_vector, y_vector, raycast.Normal)

		local adjustment_direction = raycast.Normal.Unit
		local normal_adjustment = adjustment_direction * (math.abs(preview.Size:Dot(adjustment_direction))) / 2
		
		local grid = false
		
		if grid then
			preview.Position = pc.snap_to_grid(cf.Position + normal_adjustment)
		else
			preview.Position = cf.Position + normal_adjustment
		end

	elseif not building and raycast and equipped then
		-- Handle delete box
		if raycast.Instance ~= last_selected_instance and raycast.Instance:GetAttribute("block") == player.Name then
			-- Remove delete box from the previously selected instance
			if last_selected_instance and last_selected_instance:FindFirstChild("delete_box") then
				last_selected_instance.delete_box:Destroy()
			end

			-- Add delete box to the new instance
			if not raycast.Instance:FindFirstChild("delete_box") then
				local outline = Instance.new("SelectionBox")
				outline.Name = "delete_box"
				outline.Adornee = raycast.Instance
				outline.Parent = raycast.Instance
				outline.Color3 = Color3.fromRGB(255, 0, 0)
			end

			-- Update last_selected_instance
			last_selected_instance = raycast.Instance
			
		elseif not raycast.Instance:GetAttribute("block", player.Name) then
			if last_selected_instance and last_selected_instance:FindFirstChild("delete_box") then
				last_selected_instance.delete_box:Destroy()
			end
			last_selected_instance = nil
		end
	else
		-- Clear delete box if no instance is selected
		if last_selected_instance and last_selected_instance:FindFirstChild("delete_box") then
			last_selected_instance.delete_box:Destroy()
		end
		last_selected_instance = nil
	end

	preview_toggle()
end

-- place block on server
function activate()
	if preview and building then
		script.place:Play()
		remotes.place_block:FireServer(preview.Position, selected_block.Name)
	end
	
	if last_selected_instance and not building then
		script.delete:Play()
		remotes.delete_block:FireServer(last_selected_instance)
	end
end

-- switches between build mode and delete mode
function building_toggle(value)
	building = value

	if not value and preview then
		preview:Destroy()
		preview = nil
	end
end

-- update equipped value to check if player is holding tool
function equip_toggle(value)
	equipped = value

	if value then
		hammer_gui.Parent = player.PlayerGui
	else
		hammer_gui.Parent = script
	end
end

-- sets up the block menu so u can use it
function set_up_block_menu()
	for i, block in pairs(blocks:GetChildren()) do
		local block_button = Instance.new("TextButton")
		block_button.Name = block.Name
		block_button.Text = block.Name
		block_button.TextScaled = true
		block_button.Parent = block_menu
		
		block_button.MouseButton1Click:Connect(function()
			change_block(block)
		end)
	end
end

function change_block(block)
	if block ~= selected_block then
		preview:Destroy()
		preview = nil
		selected_block = block
	end
end

-- just funny cally cally things
make_preview()
set_up_block_menu()

run_service:BindToRenderStep("preview", Enum.RenderPriority.Camera.Value, render_preview)

tool.Activated:Connect(activate)

tool.Equipped:Connect(function()
	equip_toggle(true)
end)
tool.Unequipped:Connect(function()
	equip_toggle(false)
end)

build_button.MouseButton1Click:Connect(function()
	building_toggle(true)
end)

delete_button.MouseButton1Click:Connect(function()
	building_toggle(false)
end)

PLACEMENT_CALCULATOR:

local GRID_SIZE = 1

local module = {}

function module.snap_to_grid(pos)
	return Vector3.new(
		math.round(pos.X // GRID_SIZE) * GRID_SIZE,
		math.round(pos.Y // GRID_SIZE) * GRID_SIZE,
		math.round(pos.Z // GRID_SIZE) * GRID_SIZE
	) * GRID_SIZE
end

return module