i have this module right here which is responsible for calculating its block cframe for both client and the server, the problem is that half of the block is submerged *(or whatever its called)*

i tried adding the primary part y size to the cframe but it has *a somewhat glitchy behavior*

```
-- [[ SERVICES ]]
local ReplicatedStorage = game:GetService('ReplicatedStorage')
local UserInputService = game:GetService('UserInputService')
local RunService = game:GetService('RunService')
local Players = game:GetService('Players')
-- [[ MATH FUNCTIONS ]]
local floor = math.floor
local round = math.round
-- [[ MODULE ]]
local Placement = {
constants = {
GRID_UNIT = 3;
MATRICES = {
BACK = Vector3.new(0, -1, 0);
TOP = Vector3.new(0, 0, -1);
RIGHT = Vector3.new(-1, 0, 0);
};
}
}
table.freeze(Placement.constants)
function Placement.new(canvas: BasePart)
local self = setmetatable({}, {__index = Placement})
self.canvas = canvas;
return self
end
function Placement:Raycast(mouse: Mouse, params: RaycastParams?)
local unitRay = mouse.UnitRay
return workspace:Raycast(unitRay.Origin, unitRay.Direction * 1000, params)
end
function Placement:Snap(pos: Vector3): Vector3
return Vector3.new(
round(pos.X / self.constants.GRID_UNIT) * self.constants.GRID_UNIT,
round(pos.Y / self.constants.GRID_UNIT) * self.constants.GRID_UNIT,
round(pos.Z / self.constants.GRID_UNIT) * self.constants.GRID_UNIT
)
end
function Placement:GetCFrame(model: Model, position: Vector3, normal: Vector3)
return CFrame.new(
self:Snap(position + normal)
)
end
function Placement:Colliding(model: Model, plot: Instance)
local params = OverlapParams.new()
params.FilterDescendantsInstances = {plot}
params.FilterType = Enum.RaycastFilterType.Whitelist
local parts = workspace:GetPartsInPart(model.PrimaryPart, params)
for _, part in parts do
if not model:IsAncestorOf(part) then
return true
end
end
end
function Placement:Place(...)
return ReplicatedStorage.Remotes.Place:InvokeServer(...)
end
return Placement
```

baseplate properties: