Brick26

Gear · by Brixborg

Brick26: a building tool you carry in the game. Select parts, then move, resize, turn, paint, re-surface, change the material, anchor, weld and decorate them - or make new ones. Ctrl+Z undoes.

Use it in your games: in the studio, open View > Archive, go to the Gear shelf and pick "Brick26".

The script

-- Copyright (c) 2026 Brixborg Owners. All rights reserved. See LICENSE.txt.
-- BRICK26: Brixborg's building tool. Take it out and build, in the game.
--
--   Click a part to select it. Hold Ctrl and click to add more (or to take
--   one back out). Click the sky to select nothing.
--   The last part you clicked is the FOCUS: the handles sit on it, and
--   turning goes round it.
--
--   Z  Move      drag the balls to slide the parts along
--   X  Resize    drag a ball to pull that face in or out
--   C  Rotate    drag a ball round its ring
--   V  Paint     pick a colour
--   B  Surface   studs, inlets, welds... on any face
--   N  Material  plastic, wood, neon... and see-through and shine
--   M  Anchor    held still in the air, or not
--   K  Collide   solid, or walk-through
--   J  New part  pick a shape, then click where it goes
--   G  Weld      join the parts to the focus, or break them apart
--   H  Decorate  a light, fire, smoke or sparkles
--
--   Ctrl+Z undo   Ctrl+Y redo   Ctrl+C copy the parts   Delete removes them
--
-- Locked parts and people can't be picked up. This is a LocalScript inside
-- the Brick26 Tool; the tool's Handle is the red and blue brick you hold.

-- ===========================================================================
-- THE NUMBERS (change these)
-- ===========================================================================
local STEP_STUDS = 1          -- moving and resizing go this many studs at a time (0 = smooth)
local STEP_DEGREES = 15       -- turning goes this many degrees at a time (0 = smooth)
local SMALLEST = 0.2          -- no side of a part can be made thinner than this
local NEW_PART_SIZE = Vector3.new(4, 1.2, 2) -- a new brick
local HISTORY = 60            -- how many steps Ctrl+Z can go back
local SHINE_STEP = 0.1        -- the - and + buttons for see-through and shine

-- The Brixborg look.
local BLACK = Color3.new(0.03, 0.05, 0.04)
local DARK = Color3.new(0.06, 0.12, 0.08)
local GREEN = Color3.fromRGB(70, 235, 120)
local DIM = Color3.fromRGB(40, 120, 70)
local RED = Color3.fromRGB(220, 50, 40)
local BLUE = Color3.fromRGB(40, 110, 230)

-- The colours on the paint palette.
local COLOURS = {
  "White", "Institutional white", "Light stone grey", "Medium stone grey", "Dark stone grey", "Black", "Really black", "Mid gray",
  "Bright red", "Really red", "Dusty Rose", "Medium red", "Bright orange", "Deep orange", "Br. yellowish orange", "Bright yellow",
  "New Yeller", "Cool yellow", "Lime green", "Bright green", "Dark green", "Earth green", "Camo", "Teal",
  "Toothpaste", "Cyan", "Pastel Blue", "Bright blue", "Really blue", "Navy blue", "Bright violet", "Hot pink",
  "Magenta", "Royal purple", "Lavender", "Nougat", "Brick yellow", "Brown", "Reddish brown", "Sand blue",
}
local MATERIALS = {
  "Plastic", "SmoothPlastic", "Neon", "Wood", "WoodPlanks", "Marble", "Slate", "Concrete", "Granite", "Brick",
  "Pebble", "Cobblestone", "CorrodedMetal", "DiamondPlate", "Foil", "Metal", "Grass", "Sand", "Fabric", "Ice",
}
local SURFACES = {"Smooth", "Studs", "Inlet", "Universal", "Weld", "Glue", "Hinge", "Motor", "SmoothNoOutlines"}
local SIDES = {"Top", "Bottom", "Front", "Back", "Left", "Right"}
local SHAPES = {"Block", "Ball", "Cylinder", "Wedge", "Corner", "Truss", "Seat", "Plate"}
local MODES = { -- name, key
  {"Move", "z"}, {"Resize", "x"}, {"Rotate", "c"}, {"Paint", "v"}, {"Surface", "b"}, {"Material", "n"},
  {"Anchor", "m"}, {"Collide", "k"}, {"New part", "j"}, {"Weld", "g"}, {"Decorate", "h"},
}
-- What each property of a part is remembered as, for Ctrl+Z.
local REMEMBERED = {
  "CFrame", "Size", "Color", "Material", "Transparency", "Reflectance", "Anchored", "CanCollide",
  "TopSurface", "BottomSurface", "FrontSurface", "BackSurface", "LeftSurface", "RightSurface",
}
-- ===========================================================================

local tool = script.Parent
local player = game.Players.LocalPlayer

local mouse = nil
local screen = nil        -- the ScreenGui, while the tool is out
local panel, title, status = nil, nil, nil
local modeButtons = {}
local mode = "Move"
local ctrlDown = false

local selection = {}      -- the selected parts, in the order they were picked
local boxes = {}          -- part -> its SelectionBox
local focus = nil         -- the part the handles sit on
local handles, arcs = nil, nil

local stepStuds, stepDegrees = STEP_STUDS, STEP_DEGREES
local bothSides = false   -- Resize: pull both faces out together
local eachOwnMiddle = false -- Rotate: turn each part round its own middle
local paintSide = "All"   -- Surface: which side
local newShape = "Block"  -- New part: which shape

-- ---------------------------------------------------------------------------
-- Small helpers
-- ---------------------------------------------------------------------------

local function snap(number, step)
  if step <= 0 then return number end
  return math.floor(number / step + 0.5) * step
end

-- Is this part inside someone (a model with a Humanoid)?
local function isInPerson(part)
  local up = part.Parent
  while up and up ~= workspace do
    if up:FindFirstChild("Humanoid") then return true end
    up = up.Parent
  end
  return false
end

local function canPick(part)
  return part and part:IsA("BasePart") and not part.Locked and part:IsDescendantOf(workspace) and not isInPerson(part)
end

-- The way a face of a part points, out of the part.
local function faceDirection(cframe, face)
  if face == "Right" then return cframe.rightVector end
  if face == "Left" then return -cframe.rightVector end
  if face == "Top" then return cframe.upVector end
  if face == "Bottom" then return -cframe.upVector end
  if face == "Back" then return -cframe.lookVector end
  return cframe.lookVector -- Front
end

-- Just the turn of a CFrame (no position).
local function turnOnly(cframe)
  return CFrame.new(-cframe.p) * cframe
end

local function isSelected(part)
  for _, p in ipairs(selection) do if p == part then return true end end
  return false
end

-- ---------------------------------------------------------------------------
-- Undo and redo. Every change is a list of parts' properties before and
-- after, and of things put into or taken out of the world.
-- ---------------------------------------------------------------------------
local past, future = {}, {}

local function remember(part)
  local values = {}
  for _, name in ipairs(REMEMBERED) do values[name] = part[name] end
  return values
end

-- Call before changing the parts: it notes how they are now.
local function beginChange(parts)
  local change = {props = {}, parents = {}}
  for _, part in ipairs(parts or selection) do
    table.insert(change.props, {part = part, before = remember(part)})
  end
  return change
end

-- Put something into the world (or take it out, with parent = nil), as
-- part of a change.
local function moveTo(change, thing, parent)
  table.insert(change.parents, {thing = thing, before = thing.Parent, after = parent})
  thing.Parent = parent
end

-- Call after changing the parts: the change goes on the Ctrl+Z list.
local function endChange(change)
  for _, entry in ipairs(change.props) do entry.after = remember(entry.part) end
  table.insert(past, change)
  if #past > HISTORY then table.remove(past, 1) end
  future = {}
end

local refreshAdornments -- (below)

local function apply(change, which)
  -- Parents first (in the right order), then properties.
  local list = change.parents
  if which == "before" then
    for i = #list, 1, -1 do list[i].thing.Parent = list[i].before end
  else
    for i = 1, #list do list[i].thing.Parent = list[i].after end
  end
  for _, entry in ipairs(change.props) do
    for name, value in pairs(entry[which]) do entry.part[name] = value end
  end
  -- Forget selected parts that are no longer in the world.
  local still = {}
  for _, part in ipairs(selection) do
    if part:IsDescendantOf(workspace) then table.insert(still, part) end
  end
  selection = still
  if focus and not focus:IsDescendantOf(workspace) then focus = selection[#selection] end
  refreshAdornments()
end

local function undo()
  local change = table.remove(past)
  if not change then return end
  apply(change, "before")
  table.insert(future, change)
end

local function redo()
  local change = table.remove(future)
  if not change then return end
  apply(change, "after")
  table.insert(past, change)
end

-- ---------------------------------------------------------------------------
-- What is selected, and the outlines and handles drawn on it
-- ---------------------------------------------------------------------------

local function updateStatus()
  if not status then return end
  if #selection == 0 then
    status.Text = "Click a part to select it"
  elseif #selection == 1 then
    status.Text = "1 part selected"
  else
    status.Text = #selection .. " parts selected"
  end
end

refreshAdornments = function()
  if not screen then return end
  -- An outline round every selected part (brighter round the focus).
  for part, box in pairs(boxes) do
    if not isSelected(part) then box:Destroy(); boxes[part] = nil end
  end
  for _, part in ipairs(selection) do
    local box = boxes[part]
    if not box then
      box = Instance.new("SelectionBox", screen)
      box.Adornee = part
      box.LineThickness = 0.08
      boxes[part] = box
    end
    box.Color = BrickColor.new(part == focus and "Lime green" or "Toothpaste")
  end
  -- The handles, on the focus.
  handles.Adornee = (mode == "Move" or mode == "Resize") and focus or nil
  handles.Color3 = mode == "Move" and Color3.fromRGB(255, 160, 40) or Color3.fromRGB(60, 200, 255)
  arcs.Adornee = mode == "Rotate" and focus or nil
  updateStatus()
end

local function select(part, adding)
  if not adding then selection = {} end
  if adding and isSelected(part) then
    for i, p in ipairs(selection) do if p == part then table.remove(selection, i) break end end
    focus = selection[#selection]
  else
    table.insert(selection, part)
    focus = part
  end
  refreshAdornments()
end

local function selectNothing()
  selection = {}
  focus = nil
  refreshAdornments()
end

-- ---------------------------------------------------------------------------
-- Dragging the handles
-- ---------------------------------------------------------------------------
local drag = nil -- while dragging: {change, starts = {part -> {cframe, size, anchored}}, focus}

local function startDrag()
  if #selection == 0 or not focus then return end
  drag = {change = beginChange(), starts = {}, focus = focus.CFrame}
  for _, part in ipairs(selection) do
    drag.starts[part] = {cframe = part.CFrame, size = part.Size, anchored = part.Anchored}
    part.Anchored = true -- (held still while it is being dragged)
  end
end

local function finishDrag()
  if not drag then return end
  for part, start in pairs(drag.starts) do part.Anchored = start.anchored end
  endChange(drag.change)
  drag = nil
end

local function moveDrag(face, distance)
  local along = faceDirection(drag.focus, face) * snap(distance, stepStuds)
  for part, start in pairs(drag.starts) do
    part.CFrame = start.cframe + along
  end
end

local function resizeDrag(face, distance)
  local amount = snap(distance, stepStuds)
  for part, start in pairs(drag.starts) do
    local axis = (face == "Right" or face == "Left") and "X" or (face == "Top" or face == "Bottom") and "Y" or "Z"
    local was = start.size[axis]
    local grow = bothSides and amount * 2 or amount
    local now = math.max(SMALLEST, was + grow)
    grow = now - was
    local size
    if part:IsA("Part") and part.Shape.Name == "Ball" then
      size = Vector3.new(now, now, now)
    elseif axis == "X" then
      size = Vector3.new(now, start.size.Y, start.size.Z)
    elseif axis == "Y" then
      size = Vector3.new(start.size.X, now, start.size.Z)
    else
      size = Vector3.new(start.size.X, start.size.Y, now)
    end
    part.Size = size
    -- Pulling one face keeps the opposite face where it was.
    local shift = bothSides and Vector3.new(0, 0, 0) or faceDirection(start.cframe, face) * (grow / 2)
    part.CFrame = start.cframe + shift
  end
end

local function rotateDrag(axis, angle)
  local turn = math.rad(snap(math.deg(angle), stepDegrees))
  local spin = axis == "X" and CFrame.Angles(turn, 0, 0) or axis == "Y" and CFrame.Angles(0, turn, 0) or CFrame.Angles(0, 0, turn)
  -- The same turn, but round the focus's own axis, in the world.
  local world = turnOnly(drag.focus) * spin * turnOnly(drag.focus):inverse()
  for part, start in pairs(drag.starts) do
    local middle = eachOwnMiddle and start.cframe.p or drag.focus.p
    part.CFrame = CFrame.new(middle) * world * CFrame.new(-middle) * start.cframe
  end
end

-- ---------------------------------------------------------------------------
-- Changing every selected part at once
-- ---------------------------------------------------------------------------

local function changeAll(set)
  if #selection == 0 then return end
  local change = beginChange()
  for _, part in ipairs(selection) do set(part) end
  endChange(change)
end

local function copyParts()
  if #selection == 0 then return end
  local change = beginChange({})
  local copies = {}
  for _, part in ipairs(selection) do
    local copy = part:Clone()
    moveTo(change, copy, part.Parent)
    table.insert(copies, copy)
  end
  endChange(change)
  selection = copies
  focus = copies[#copies]
  refreshAdornments()
end

local function deleteParts()
  if #selection == 0 then return end
  local change = beginChange({})
  for _, part in ipairs(selection) do moveTo(change, part, nil) end
  endChange(change)
  selectNothing()
end

-- Add (or take away) a Fire, Smoke... in every selected part.
local function decorate(className)
  if #selection == 0 then return end
  local taking = focus and focus:FindFirstChildOfClass(className) ~= nil
  local change = beginChange({})
  for _, part in ipairs(selection) do
    local found = part:FindFirstChildOfClass(className)
    if taking and found then
      moveTo(change, found, nil)
    elseif not taking and not found then
      moveTo(change, Instance.new(className), part)
    end
  end
  endChange(change)
end

local function weldToFocus()
  if not focus or #selection < 2 then return end
  local change = beginChange({})
  for _, part in ipairs(selection) do
    if part ~= focus then
      local weld = Instance.new("Weld")
      weld.Name = "Brick26Weld"
      weld.Part0 = focus
      weld.Part1 = part
      weld.C0 = focus.CFrame:inverse() * part.CFrame
      moveTo(change, weld, focus)
    end
  end
  endChange(change)
end

local function breakWelds()
  if #selection == 0 then return end
  local change = beginChange({})
  for _, joint in ipairs(workspace:GetDescendants()) do
    if joint:IsA("JointInstance") and (isSelected(joint.Part0) or isSelected(joint.Part1)) then
      moveTo(change, joint, nil)
    end
  end
  endChange(change)
end

-- A new part where the pointer is, sitting on the face it points at.
local function placeNewPart()
  local target = mouse.Target
  if not target then return end
  local part
  if newShape == "Wedge" then part = Instance.new("WedgePart")
  elseif newShape == "Corner" then part = Instance.new("CornerWedgePart")
  elseif newShape == "Truss" then part = Instance.new("TrussPart")
  elseif newShape == "Seat" then part = Instance.new("Seat")
  else part = Instance.new("Part") end
  local size = NEW_PART_SIZE
  if newShape == "Ball" then part.Shape = Enum.PartType.Ball; size = Vector3.new(2, 2, 2) end
  if newShape == "Cylinder" then part.Shape = Enum.PartType.Cylinder; size = Vector3.new(2, 2, 2) end
  if newShape == "Truss" then size = Vector3.new(2, 2, 2) end
  if newShape == "Corner" then size = Vector3.new(2, 2, 2) end
  if newShape == "Seat" then size = Vector3.new(2, 1, 2) end
  if newShape == "Plate" then size = Vector3.new(4, 0.4, 2) end
  part.Size = size
  part.Anchored = true
  part.BrickColor = BrickColor.new("Medium stone grey")
  -- Out from the face pointed at, by half the new part's thickness that way.
  local out = faceDirection(target.CFrame, mouse.TargetSurface.Name)
  local half = math.abs(out.X) * size.X / 2 + math.abs(out.Y) * size.Y / 2 + math.abs(out.Z) * size.Z / 2
  local at = mouse.Hit.p + out * half
  -- Lined up on the grid, except in the direction it sits out.
  local function grid(n, o) if math.abs(o) > 0.5 then return n end return snap(n, stepStuds) end
  at = Vector3.new(grid(at.X, out.X), grid(at.Y, out.Y), grid(at.Z, out.Z))
  part.CFrame = CFrame.new(at)
  local change = beginChange({})
  moveTo(change, part, workspace)
  endChange(change)
  select(part, false)
end

-- ---------------------------------------------------------------------------
-- The panel
-- ---------------------------------------------------------------------------

local function label(parent, text, x, y, w, h, colour)
  local l = Instance.new("TextLabel", parent)
  l.Position = UDim2.new(0, x, 0, y)
  l.Size = UDim2.new(0, w, 0, h)
  l.BackgroundTransparency = 1
  l.TextColor3 = colour or GREEN
  l.Font = Enum.Font.SourceSansBold
  l.FontSize = Enum.FontSize.Size14
  l.TextXAlignment = Enum.TextXAlignment.Left
  l.Text = text
  return l
end

local function button(parent, text, x, y, w, h, onClick)
  local b = Instance.new("TextButton", parent)
  b.Position = UDim2.new(0, x, 0, y)
  b.Size = UDim2.new(0, w, 0, h)
  b.BackgroundColor3 = DARK
  b.BorderColor3 = DIM
  b.TextColor3 = GREEN
  b.Font = Enum.Font.SourceSansBold
  b.FontSize = Enum.FontSize.Size14
  b.Text = text
  b.MouseButton1Click:connect(onClick)
  return b
end

-- A box to type a number in. `get` reads it, `set` stores a new one.
local function numberBox(parent, text, x, y, get, set)
  label(parent, text, x, y, 110, 24)
  local box = Instance.new("TextBox", parent)
  box.Position = UDim2.new(0, x + 116, 0, y)
  box.Size = UDim2.new(0, 60, 0, 24)
  box.BackgroundColor3 = BLACK
  box.BorderColor3 = GREEN
  box.TextColor3 = GREEN
  box.Font = Enum.Font.SourceSansBold
  box.FontSize = Enum.FontSize.Size14
  box.ClearTextOnFocus = false
  box.Text = tostring(get())
  box.FocusLost:connect(function()
    local n = tonumber(box.Text)
    if n and n >= 0 then set(n) end
    box.Text = tostring(get())
  end)
end

-- A row of choices, one of them lit.
local function choices(parent, list, x, y, w, h, columns, current, choose)
  local buttons = {}
  for i, name in ipairs(list) do
    local column = (i - 1) % columns
    local row = math.floor((i - 1) / columns)
    local b = button(parent, name, x + column * (w + 4), y + row * (h + 4), w, h, function()
      choose(name)
      for n, other in pairs(buttons) do other.BorderColor3 = n == name and GREEN or DIM end
    end)
    b.BorderColor3 = name == current and GREEN or DIM
    buttons[name] = b
  end
  return y + math.ceil(#list / columns) * (h + 4)
end

local showMode -- (below)

-- What is on the panel for each mode.
local function fillPanel(body)
  local y = 0
  if mode == "Move" then
    label(body, "Drag the orange balls.", 0, y, 200, 20); y = y + 26
    numberBox(body, "Step (studs)", 0, y, function() return stepStuds end, function(n) stepStuds = n end)
  elseif mode == "Resize" then
    label(body, "Drag a blue ball to pull its face.", 0, y, 200, 20); y = y + 26
    numberBox(body, "Step (studs)", 0, y, function() return stepStuds end, function(n) stepStuds = n end); y = y + 32
    choices(body, {"One side", "Both sides"}, 0, y, 92, 24, 2, bothSides and "Both sides" or "One side",
      function(name) bothSides = name == "Both sides" end)
  elseif mode == "Rotate" then
    label(body, "Drag a ball round its ring.", 0, y, 200, 20); y = y + 26
    numberBox(body, "Step (degrees)", 0, y, function() return stepDegrees end, function(n) stepDegrees = n end); y = y + 32
    label(body, "Turn round:", 0, y, 200, 20); y = y + 22
    choices(body, {"The focus", "Each part"}, 0, y, 92, 24, 2, eachOwnMiddle and "Each part" or "The focus",
      function(name) eachOwnMiddle = name == "Each part" end)
  elseif mode == "Paint" then
    for i, name in ipairs(COLOURS) do
      local column, row = (i - 1) % 8, math.floor((i - 1) / 8)
      local swatch = button(body, "", column * 24, row * 24, 20, 20, function()
        changeAll(function(part) part.BrickColor = BrickColor.new(name) end)
      end)
      swatch.BackgroundColor3 = BrickColor.new(name).Color
      swatch.BorderColor3 = BLACK
    end
  elseif mode == "Surface" then
    label(body, "Which side:", 0, y, 200, 20); y = y + 22
    local sides = {"All"}
    for _, s in ipairs(SIDES) do table.insert(sides, s) end
    y = choices(body, sides, 0, y, 46, 22, 4, paintSide, function(name) paintSide = name end) + 6
    label(body, "Make it:", 0, y, 200, 20); y = y + 22
    for i, kind in ipairs(SURFACES) do
      local column, row = (i - 1) % 2, math.floor((i - 1) / 2)
      button(body, kind, column * 96, y + row * 26, 92, 22, function()
        changeAll(function(part)
          for _, side in ipairs(SIDES) do
            if paintSide == "All" or paintSide == side then part[side .. "Surface"] = Enum.SurfaceType[kind] end
          end
        end)
      end)
    end
  elseif mode == "Material" then
    for i, name in ipairs(MATERIALS) do
      local column, row = (i - 1) % 2, math.floor((i - 1) / 2)
      button(body, name, column * 96, row * 22, 92, 20, function()
        changeAll(function(part) part.Material = Enum.Material[name] end)
      end)
    end
    y = 10 * 22 + 6
    for i, what in ipairs({"Transparency", "Reflectance"}) do
      label(body, what == "Transparency" and "See-through" or "Shine", 0, y, 90, 22)
      button(body, "-", 100, y, 40, 22, function()
        changeAll(function(part) part[what] = math.max(0, part[what] - SHINE_STEP) end)
      end)
      button(body, "+", 146, y, 40, 22, function()
        changeAll(function(part) part[what] = math.min(1, part[what] + SHINE_STEP) end)
      end)
      y = y + 26
    end
  elseif mode == "Anchor" then
    button(body, "Anchor (held still)", 0, y, 188, 28, function() changeAll(function(p) p.Anchored = true end) end); y = y + 34
    button(body, "Unanchor (can fall)", 0, y, 188, 28, function() changeAll(function(p) p.Anchored = false end) end)
  elseif mode == "Collide" then
    button(body, "Solid", 0, y, 188, 28, function() changeAll(function(p) p.CanCollide = true end) end); y = y + 34
    button(body, "Walk-through", 0, y, 188, 28, function() changeAll(function(p) p.CanCollide = false end) end)
  elseif mode == "New part" then
    label(body, "Pick a shape, then click", 0, y, 200, 20); y = y + 18
    label(body, "where it goes.", 0, y, 200, 20); y = y + 26
    choices(body, SHAPES, 0, y, 92, 24, 2, newShape, function(name) newShape = name end)
  elseif mode == "Weld" then
    label(body, "Joins the parts to the focus.", 0, y, 200, 20); y = y + 26
    button(body, "Weld to focus", 0, y, 188, 28, weldToFocus); y = y + 34
    button(body, "Break welds", 0, y, 188, 28, breakWelds)
  elseif mode == "Decorate" then
    label(body, "Click again to take it off.", 0, y, 200, 20); y = y + 26
    for _, kind in ipairs({{"Light", "PointLight"}, {"Fire", "Fire"}, {"Smoke", "Smoke"}, {"Sparkles", "Sparkles"}}) do
      button(body, kind[1], 0, y, 188, 28, function() decorate(kind[2]) end)
      y = y + 34
    end
  end
end

showMode = function(name)
  mode = name
  if not screen then return end
  title.Text = "BRICK26 - " .. string.upper(name)
  for n, b in pairs(modeButtons) do
    b.BackgroundColor3 = n == name and DIM or DARK
    b.TextColor3 = n == name and BLACK or GREEN
  end
  local old = panel:FindFirstChild("Body")
  if old then old:Destroy() end
  local body = Instance.new("Frame", panel)
  body.Name = "Body"
  body.BackgroundTransparency = 1
  body.Position = UDim2.new(0, 10, 0, 56)
  body.Size = UDim2.new(1, -20, 1, -100)
  fillPanel(body)
  refreshAdornments()
end

local function buildScreen()
  screen = Instance.new("ScreenGui")
  screen.Name = "Brick26"

  -- The column of modes, down the right edge.
  local column = Instance.new("Frame", screen)
  column.Position = UDim2.new(1, -96, 0.5, -214)
  column.Size = UDim2.new(0, 86, 0, 11 * 34 + 8)
  column.BackgroundColor3 = BLACK
  column.BorderColor3 = GREEN
  modeButtons = {}
  for i, m in ipairs(MODES) do
    modeButtons[m[1]] = button(column, string.upper(m[2]) .. "  " .. m[1], 4, 4 + (i - 1) * 34, 78, 30, function() showMode(m[1]) end)
  end

  -- The panel for the mode, beside it.
  panel = Instance.new("Frame", screen)
  panel.Position = UDim2.new(1, -312, 0.5, -214)
  panel.Size = UDim2.new(0, 208, 0, 11 * 34 + 8)
  panel.BackgroundColor3 = BLACK
  panel.BorderColor3 = GREEN
  -- A red and blue stripe, like the brick.
  local stripe = Instance.new("Frame", panel)
  stripe.Size = UDim2.new(0.5, 0, 0, 4)
  stripe.BackgroundColor3 = RED
  stripe.BorderSizePixel = 0
  local stripe2 = Instance.new("Frame", panel)
  stripe2.Position = UDim2.new(0.5, 0, 0, 0)
  stripe2.Size = UDim2.new(0.5, 0, 0, 4)
  stripe2.BackgroundColor3 = BLUE
  stripe2.BorderSizePixel = 0
  title = label(panel, "BRICK26", 10, 8, 190, 22)
  title.FontSize = Enum.FontSize.Size18
  status = label(panel, "", 10, 30, 190, 20, DIM)

  -- Along the bottom: copy, delete, undo, redo.
  local bottom = 11 * 34 + 8 - 34
  button(panel, "Copy", 6, bottom, 46, 28, copyParts)
  button(panel, "Delete", 56, bottom, 46, 28, deleteParts)
  button(panel, "Undo", 106, bottom, 46, 28, undo)
  button(panel, "Redo", 156, bottom, 46, 28, redo)

  -- The handles (they only show when they have a part to sit on).
  handles = Instance.new("Handles", screen)
  arcs = Instance.new("ArcHandles", screen)
  handles.MouseButton1Down:connect(startDrag)
  handles.MouseButton1Up:connect(finishDrag)
  handles.MouseDrag:connect(function(face, distance)
    if not drag then return end
    if mode == "Move" then moveDrag(face.Name, distance) else resizeDrag(face.Name, distance) end
  end)
  arcs.MouseButton1Down:connect(startDrag)
  arcs.MouseButton1Up:connect(finishDrag)
  arcs.MouseDrag:connect(function(axis, angle)
    if drag then rotateDrag(axis.Name, angle) end
  end)

  screen.Parent = player:FindFirstChild("PlayerGui") or Instance.new("PlayerGui", player)
  boxes = {}
  showMode(mode)
end

-- ---------------------------------------------------------------------------
-- Taking it out and putting it away
-- ---------------------------------------------------------------------------
local connections = {}

tool.Equipped:connect(function(theMouse)
  mouse = theMouse or player:GetMouse()
  buildScreen()
  table.insert(connections, mouse.Button1Down:connect(function()
    if mode == "New part" then placeNewPart() return end
    local target = mouse.Target
    if canPick(target) then
      select(target, ctrlDown)
    elseif not ctrlDown then
      selectNothing()
    end
  end))
  table.insert(connections, mouse.KeyDown:connect(function(key)
    if key == "ctrl" then ctrlDown = true return end
    if key == "delete" then deleteParts() return end
    if ctrlDown then
      if key == "z" then undo() elseif key == "y" then redo() elseif key == "c" then copyParts() end
      return
    end
    for _, m in ipairs(MODES) do
      if key == m[2] then showMode(m[1]) end
    end
  end))
  table.insert(connections, mouse.KeyUp:connect(function(key)
    if key == "ctrl" then ctrlDown = false end
  end))
end)

tool.Unequipped:connect(function()
  finishDrag()
  for _, c in ipairs(connections) do c:disconnect() end
  connections = {}
  if screen then screen:Destroy() end
  screen = nil
  boxes = {}
  selection = {}
  focus = nil
  ctrlDown = false
end)

More gear