774 lines
22 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

local number = require("Number")
local GUI = require("GUI")
local screen = require("Screen")
local color = require("Color")
local system = require("System")
local paths = require("Paths")
local text = require("Text")
local bigLetters = require("BigLetters")
local filesystem = require("Filesystem")
--------------------------------------------------------------------------------
local args, options = system.parseArguments(...)
local proxies = {}
local function updateProxy(name)
proxies[name] = component.list(name)()
if proxies[name] then
proxies[name] = component.proxy(proxies[name])
return proxies[name]
end
end
local function print(model)
local proxy = proxies.printer3d
proxy.reset()
if model.label then
proxy.setLabel(model.label)
end
if model.tooltip then
proxy.setTooltip(model.tooltip)
end
if model.collidable then
proxy.setCollidable(model.collidable[1], model.collidable[2])
end
if model.lightLevel then
proxy.setLightLevel(model.lightLevel)
end
if model.emitRedstone then
proxy.setRedstoneEmitter(model.emitRedstone)
end
if model.buttonMode then
proxy.setButtonMode(model.buttonMode)
end
for i = 1, #model.shapes do
local shape = model.shapes[i]
proxy.addShape(shape[1], shape[2], shape[3], shape[4], shape[5], shape[6], shape.texture or "empty", shape.state, shape.tint)
end
local success, reason = proxy.commit(1)
if not success then
GUI.alert(localization.failedToPrint .. ": " .. reason)
end
end
-- Just printing without UI
if options.p then
updateProxy("printer3d")
print(filesystem.readTable(args[1]))
return
end
--------------------------------------------------------------------------------
local currentScriptDirectory = filesystem.path(system.getCurrentScript())
local localization = system.getLocalization(currentScriptDirectory .. "Localizations/")
local currentLayer = 0
local viewPixelWidth, viewPixelHeight = 4, 2
local model
local savePath
local shapeLimit
local workspace, window, menu = system.addWindow(GUI.filledWindow(1, 1, 100, screen.getHeight() - 1, 0x1E1E1E))
--------------------------------------------------------------------------------
local toolPanel = window:addChild(GUI.panel(1, 1, 28, 1, 0x2D2D2D))
window.backgroundPanel.localX = toolPanel.width + 1
local toolLayout = window:addChild(GUI.layout(1, 1, toolPanel.width, 1, 1, 1))
toolLayout:setAlignment(1, 1, GUI.ALIGNMENT_HORIZONTAL_CENTER, GUI.ALIGNMENT_VERTICAL_TOP)
toolLayout:setMargin(1, 1, 0, 1)
local function addSeparator(text)
toolLayout:addChild(GUI.object(1, 1, toolLayout.width, 1)).draw = function(object)
screen.drawRectangle(object.x, object.y, object.width, 1, 0x0F0F0F, 0xE1E1E1, " ")
screen.drawText(object.x + 1, object.y, 0xE1E1E1, text)
end
end
local function newButton(width, height, ...)
local button = GUI.button(1, 1, width, height, 0x3C3C3C, 0xA5A5A5, 0xE1E1E1, 0x3C3C3C, ...)
button.colors.disabled.background = 0x3C3C3C
button.colors.disabled.text = 0x5A5A5A
return button
end
local function addObjectsTo(layout, objects)
layout:setGridSize(#objects * 2 - 1, 1)
for i = 1, #objects do
layout:setColumnWidth(i * 2 - 1, GUI.SIZE_POLICY_RELATIVE, 1 / #objects)
if i < #objects then
layout:setColumnWidth(i * 2, GUI.SIZE_POLICY_ABSOLUTE, 1)
end
layout:setPosition(i * 2 - 1, 1, layout:addChild(objects[i]))
layout:setFitting(i * 2 - 1, 1, true, false)
end
end
local function addObjectsWithLayout(objects)
addObjectsTo(toolLayout:addChild(GUI.layout(1, 1, toolLayout.width - 2, 1, 1, 1)), objects)
end
local function addButtons(...)
local texts, buttons = {...}, {}
for i = 1, #texts do
buttons[i] = newButton(toolLayout.width - 2, 1, texts[i])
end
addObjectsWithLayout(buttons)
return table.unpack(buttons)
end
local function addSwitch(...)
return toolLayout:addChild(GUI.switchAndLabel(1, 1, toolLayout.width - 2, 6, 0x66DB80, 0x1E1E1E, 0xE1E1E1, 0x787878, ...)).switch
end
local function addInput(...)
return toolLayout:addChild(GUI.input(1, 1, toolLayout.width - 2, 1, 0x1E1E1E, 0xA5A5A5, 0x5A5A5A, 0x1E1E1E, 0xE1E1E1, ...))
end
local function addSlider(...)
return toolLayout:addChild(GUI.slider(1, 1, toolLayout.width - 2, 0x66DB80, 0x1E1E1E, 0xE1E1E1, 0x787878, ...))
end
local function addComboBox(...)
return toolLayout:addChild(GUI.comboBox(1, 1, toolLayout.width - 2, 1, 0x1E1E1E, 0xA5A5A5, 0x3C3C3C, 0x696969))
end
local bigContainer = toolLayout:addChild(GUI.container(1, 1, toolLayout.width, 5))
bigContainer:addChild(GUI.object(1, 1, bigContainer.width, bigContainer.height)).draw = function(object)
local text = tostring(math.floor(currentLayer))
local width = bigLetters.getTextSize(text)
bigLetters.drawText(math.floor(object.x + object.width / 2 - width / 2), object.y, 0xE1E1E1, text)
end
window.actionButtons:remove()
bigContainer:addChild(window.actionButtons)
window.actionButtons.localY = 1
local fileItem = menu:addContextMenuItem(localization.file)
local newItem = fileItem:addItem("", localization.new, false, "^N")
local openItem = fileItem:addItem("📂", localization.open, false, "^O")
fileItem:addSeparator()
local saveItem = fileItem:addItem("💾", localization.save, true, "^S")
local saveAsItem = fileItem:addItem("🖪", localization.saveAs, false, "^⇧S")
menu:addItem(localization.help).onTouch = function()
local container = GUI.addBackgroundContainer(workspace, true, true, localization.help)
local textBox = container.layout:addChild(GUI.textBox(1, 1, 68, 1, nil, 0xB4B4B4, localization.helpInfo, 1, 0, 0, true, true))
textBox.eventHandler = container.panel.eventHandler
textBox:update()
workspace:draw()
end
local function updateSavePath(path)
savePath = path
saveItem.disabled = not savePath
end
addSeparator(localization.elementSettings)
local printButton = window:addChild(newButton(toolLayout.width, 3, localization.print))
local modelList = toolLayout:addChild(GUI.list(1, 1, toolLayout.width, 3, math.floor(toolLayout.width / 2), 0, 0x1E1E1E, 0x5A5A5A, 0x1E1E1E, 0x5A5A5A, 0x2D2D2D, 0xA5A5A5))
modelList:setAlignment(GUI.ALIGNMENT_HORIZONTAL_CENTER, GUI.ALIGNMENT_VERTICAL_TOP)
modelList:setDirection(GUI.DIRECTION_HORIZONTAL)
local disabledListItem = modelList:addItem(localization.disabled)
local enabledListItem = modelList:addItem(localization.enabled)
local shapesComboBox = addComboBox()
local textureInput = addInput("", localization.texture, true)
local tintColorSelector = toolLayout:addChild(GUI.colorSelector(1, 1, toolLayout.width - 2, 1, 0x330040, localization.tintColor))
local tintSwitch = addSwitch(localization.tintEnabled .. ":", false)
local function checkShapeState(shape)
return modelList.selectedItem == 1 and not shape.state or modelList.selectedItem == 2 and shape.state
end
local addShapeButton, removeShapeButton = addButtons(localization.add, localization.remove)
addSeparator(localization.blockSettings)
local labelInput = addInput("", localization.label, true)
local tooltipInput = addInput("", localization.tooltip, true)
local buttonModeSwitch = addSwitch(localization.buttonMode .. ":", false)
local collisionSwitch = addSwitch(localization.collidable .. ":", true)
local redstoneSwitch = addSwitch(localization.emitRedstone .. ":", true)
local lightLevelSlider = addSlider(0, 15, 0, false, localization.lightLevel .. ": ", "")
lightLevelSlider.height = 2
lightLevelSlider.roundValues = true
local axisComboBox = addComboBox()
axisComboBox:addItem(localization.xAxis)
axisComboBox:addItem(localization.yAxis)
axisComboBox:addItem(localization.zAxis)
local function fixShape(shape)
for i = 1, 3 do
if shape[i] > shape[i + 3] then
shape[i], shape[i + 3] = shape[i + 3], shape[i]
end
end
end
local rotateButton, flipButton = addButtons(localization.rotate, localization.flip)
addSeparator(localization.projectorSettings)
local projectorSwitch = addSwitch(localization.projectorEnabled .. ": ", true)
local projectorScaleSlider = addSlider(0.33, 3, proxies.hologram and proxies.hologram.getScale() or 1, false, localization.scale .. ": ", "")
projectorScaleSlider.onValueChanged = function()
if proxies.hologram then
proxies.hologram.setScale(projectorScaleSlider.value)
end
end
local projectorOffsetSlider = addSlider(0, 1, proxies.hologram and select(2, proxies.hologram.getTranslation()) or 0, false, localization.offset .. ": ", "")
projectorOffsetSlider.height = 2
projectorOffsetSlider.onValueChanged = function()
if proxies.hologram then
proxies.hologram.setTranslation(0, projectorOffsetSlider.value, 0)
end
end
local hologramWidgetsLayout = toolLayout:addChild(GUI.layout(1, 1, toolLayout.width - 2, 1, 1, 1))
local function updateHologramWidgets()
local objects = {}
for i = 1, (proxies.hologram and proxies.hologram.maxDepth() == 1 and 1 or 3) or 3 do
objects[i] = GUI.colorSelector(1, 1, 1, 1, proxies.hologram and proxies.hologram.getPaletteColor(i) or 0x0, localization.color .. i)
objects[i].onColorSelected = function()
if proxies.hologram then
proxies.hologram.setPaletteColor(i, objects[i].color)
workspace:draw()
end
end
end
hologramWidgetsLayout:removeChildren()
addObjectsTo(hologramWidgetsLayout, objects)
end
local function updateAddRemoveButtonsState()
addShapeButton.disabled = #model.shapes >= shapeLimit
removeShapeButton.disabled = #model.shapes < 1 or shapesComboBox:count() < 1
end
local function updateComboBoxFromModel()
shapesComboBox:clear()
for i = 1, #model.shapes do
if checkShapeState(model.shapes[i]) then
local item = shapesComboBox:addItem(tostring(i))
item.shapeIndex = i
item.color = colors[i]
end
end
end
local function updateProxies()
updateProxy("hologram")
updateHologramWidgets()
local printerProxy = updateProxy("printer3d")
--Update shape limit if we have a printer connected.
--Probably halfway laggy because it's updating colors now too though,
--but necessary if we want things to work right with no unintended consequences.
--I would have this update in the component add remove part, but updateProxy is
--awesome and tells us if a component is present or not.
local newLimit = printerProxy and printerProxy.getMaxShapeCount() or 24
--No need to update anything if new limit is the same as old.
if newLimit ~= shapeLimit then
--Otherwise, we need to update both limit and all the colors that use the limit.
shapeLimit = newLimit
colors, hue, hueStep = {}, 0, 360 / shapeLimit
for i = 1, shapeLimit do
colors[i] = color.HSBToInteger(hue, 1, 1)
hue = hue + hueStep
end
-- Truncating existing model if it's too fat chick
if model and #model.shapes > newLimit then
while #model.shapes > newLimit do
table.remove(model.shapes, #model.shapes)
end
updateComboBoxFromModel()
end
end
printButton.disabled = not printerProxy
end
updateProxies()
local function getSelectedShapeIndex()
local item = shapesComboBox:getItem(shapesComboBox.selectedItem)
return item and item.shapeIndex
end
local function updateHologram()
if proxies.hologram and projectorSwitch.state then
local initialX = 17
local initialY = 2
local initialZ = 33
local projectorPaletteIndex = proxies.hologram.maxDepth() > 1 and 3 or 1
proxies.hologram.clear()
local shapeIndex = getSelectedShapeIndex()
for i = 1, #model.shapes do
local shape = model.shapes[i]
if checkShapeState(shape) then
for x = initialX + shape[1], initialX + shape[4] - 1 do
for z = initialZ - shape[6] + 1, initialZ - shape[3] do
proxies.hologram.fill(x, z, initialY + shape[2], initialY + shape[5] - 1, projectorPaletteIndex == 3 and (i == shapeIndex and 1 or 2) or 1)
end
end
end
end
proxies.hologram.fill(initialX - 1, initialZ - currentLayer, initialY - 1, initialY + 16, projectorPaletteIndex)
proxies.hologram.fill(initialX + 16, initialZ - currentLayer, initialY - 1, initialY + 16, projectorPaletteIndex)
for x = initialX - 1, initialX + 16 do
proxies.hologram.set(x, initialY - 1, initialZ - currentLayer, projectorPaletteIndex)
proxies.hologram.set(x, initialY + 16, initialZ - currentLayer, projectorPaletteIndex)
end
end
end
local function updateWidgetsFromModel()
labelInput.text = model.label or ""
tooltipInput.text = model.tooltip or ""
buttonModeSwitch:setState(model.buttonMode)
collisionSwitch:setState(model.collidable)
redstoneSwitch.state = model.emitRedstone or false
lightLevelSlider.value = model.lightLevel or 0
local shapeIndex = getSelectedShapeIndex()
if shapeIndex then
textureInput.text = model.shapes[shapeIndex].texture or ""
tintSwitch:setState(model.shapes[shapeIndex].tint and true or false)
tintColorSelector.color = model.shapes[shapeIndex].tint or tintColorSelector.color
end
end
local function updateModelFromWidgets()
model.label = #labelInput.text > 0 and labelInput.text or nil
model.tooltip = #tooltipInput.text > 0 and tooltipInput.text or nil
model.buttonMode = buttonModeSwitch.state
model.collidable = collisionSwitch.state and {true, true} or nil
model.emitRedstone = redstoneSwitch.state
model.lightLevel = lightLevelSlider.value > 0 and lightLevelSlider.value or nil
local shapeIndex = getSelectedShapeIndex()
if shapeIndex then
model.shapes[shapeIndex].texture = #textureInput.text > 0 and textureInput.text or nil
model.shapes[shapeIndex].tint = tintSwitch.state and tintColorSelector.color or nil
end
end
local function load(path)
model = filesystem.readTable(path)
updateSavePath(path)
updateComboBoxFromModel()
updateWidgetsFromModel()
updateAddRemoveButtonsState()
end
local function save(path)
filesystem.writeTable(path, model, true)
updateSavePath(path)
end
saveItem.onTouch = function()
save(savePath)
end
saveAsItem.onTouch = function()
local filesystemDialog = GUI.addFilesystemDialog(workspace, true, 50, math.floor(window.height * 0.8), "Save", "Cancel", "File name", "/")
filesystemDialog:setMode(GUI.IO_MODE_SAVE, GUI.IO_MODE_FILE)
filesystemDialog:addExtensionFilter(".3dm")
filesystemDialog:expandPath(paths.user.desktop)
filesystemDialog.filesystemTree.selectedItem = paths.user.desktop
filesystemDialog.onSubmit = function(path)
save(path)
end
filesystemDialog:show()
end
openItem.onTouch = function()
local filesystemDialog = GUI.addFilesystemDialog(workspace, true, 50, math.floor(window.height * 0.8), "Open", "Cancel", "File name", "/")
filesystemDialog:setMode(GUI.IO_MODE_OPEN, GUI.IO_MODE_FILE)
filesystemDialog:addExtensionFilter(".3dm")
filesystemDialog:expandPath(paths.user.desktop)
filesystemDialog.onSubmit = function(path)
load(path)
workspace:draw()
updateHologram()
end
filesystemDialog:show()
end
local viewLayout = window:addChild(GUI.layout(window.backgroundPanel.localX, 1, 1, 1, 1, 1))
viewLayout:setSpacing(1, 1, 2)
local view = viewLayout:addChild(GUI.object(1, 1, 16 * viewPixelWidth, 16 * viewPixelHeight))
local function getShapeDrawingData(shape)
local width, height =
(shape[4] - shape[1]) * viewPixelWidth,
(shape[5] - shape[2]) * viewPixelHeight
return
width > 0 and height > 0 and currentLayer >= shape[3] and currentLayer <= shape[6] - 1 and checkShapeState(shape),
view.x + shape[1] * viewPixelWidth,
view.y + view.height - shape[2] * viewPixelHeight - height,
width,
height
end
view.draw = function()
local x, y, step = view.x, view.y, true
for j = 1, 16 do
for i = 1, 16 do
screen.drawRectangle(x, y, viewPixelWidth, viewPixelHeight, 0xF0F0F0, 0xE1E1E1, step and " " or "")
x, step = x + viewPixelWidth, not step
end
x, y, step = view.x, y + viewPixelHeight, not step
end
GUI.drawShadow(view.x, view.y, view.width, view.height, nil, true)
local selectedShape, shape = getSelectedShapeIndex()
if selectedShape then
for i = 1, #model.shapes do
shape = model.shapes[i]
local focused, x, y, width, height = getShapeDrawingData(shape)
if focused then
screen.drawRectangle(x, y, width, height, i == selectedShape and colors[i] or color.blend(colors[i], 0xFFFFFF, 0.4), 0x0, " ")
if currentLayer == shape[3] then
screen.drawRectangle(x, y, viewPixelWidth, viewPixelHeight, 0x0, 0x0, " ", i == selectedShape and 0.2 or 0.6)
end
if currentLayer == shape[6] - 1 then
screen.drawRectangle(x + width - viewPixelWidth, y + height - viewPixelHeight, viewPixelWidth, viewPixelHeight, 0x0, 0x0, " ", i == shapeIndex and 0.4 or 0.8)
end
end
end
end
end
toolLayout.eventHandler = function(workspace, toolLayout, e1, e2, e3, e4, e5)
if e1 == "scroll" then
local h, v = toolLayout:getMargin(1, 1)
if e5 > 0 then
if v < 1 then
v = v + 1
toolLayout:setMargin(1, 1, h, v)
workspace:draw()
end
else
local child = toolLayout.children[#toolLayout.children]
if child.localY + child.height - 1 >= toolLayout.localY + toolLayout.height - 1 then
v = v - 1
toolLayout:setMargin(1, 1, h, v)
workspace:draw()
end
end
end
end
local shapeX, shapeY, shapeZ
view.eventHandler = function(workspace, view, e1, e2, e3, e4, e5)
if e1 == "touch" or e1 == "drag" then
e3, e4 = math.ceil(e3), math.ceil(e4)
if e5 == 0 then
local selectedShape = getSelectedShapeIndex()
if selectedShape then
local shape = model.shapes[selectedShape]
local x = math.floor((e3 - view.x) / view.width * 16)
local y = 15 - math.floor((e4 - view.y) / view.height * 16)
if e1 == "touch" then
shapeX, shapeY, shapeZ = x, y, currentLayer
shape[1], shape[2], shape[3] = x, y, currentLayer
shape[4], shape[5], shape[6] = x + 1, y + 1, currentLayer + 1
elseif shapeX then
shape[1], shape[2], shape[3] = shapeX, shapeY, shapeZ
shape[4], shape[5], shape[6] = x, y, currentLayer
fixShape(shape)
shape[4], shape[5], shape[6] = shape[4] + 1, shape[5] + 1, shape[6] + 1
end
workspace:draw()
end
else
-- Selecting shape
local shape
for i = #model.shapes, 1, -1 do
shape = model.shapes[i]
local focused, x, y, width, height = getShapeDrawingData(shape)
if focused and e3 >= x and e3 <= x + width - 1 and e4 >= y and e4 <= y + height - 1 then
for j = 1, shapesComboBox:count() do
if shapesComboBox:getItem(j).shapeIndex == i then
shapesComboBox.selectedItem = j
workspace:draw()
break
end
end
break
end
end
end
elseif e1 == "drop" then
shapeX, shapeY, shapeZ = nil, nil, nil
updateHologram()
elseif e1 == "scroll" then
local function fix()
local shapeIndex = getSelectedShapeIndex()
if shapeX and shapeIndex then
local shape = model.shapes[shapeIndex]
shape[3] = shapeZ
shape[6] = currentLayer
fixShape(shape)
shape[6] = shape[6] + 1
end
end
if e5 > 0 then
if currentLayer < 15 then
currentLayer = currentLayer + 1
fix()
workspace:draw()
updateHologram()
end
else
if currentLayer > 0 then
currentLayer = currentLayer - 1
fix()
workspace:draw()
updateHologram()
end
end
elseif (e1 == "component_added" or e1 == "component_removed") and (e3 == "printer3d" or e3 == "hologram") then
updateProxies()
updateAddRemoveButtonsState()
workspace:draw()
updateHologram()
end
end
rotateButton.onTouch = function()
for i = 1, #model.shapes do
local shape = model.shapes[i]
if axisComboBox.selectedItem == 1 then
shape[1], shape[2], shape[3], shape[4], shape[5], shape[6] = shape[1], -shape[3] + 16, shape[2], shape[4], -shape[6] + 16, shape[5]
elseif axisComboBox.selectedItem == 2 then
shape[1], shape[2], shape[3], shape[4], shape[5], shape[6] = -shape[3] + 16, shape[2], shape[1], -shape[6] + 16, shape[5], shape[4]
else
shape[1], shape[2], shape[3], shape[4], shape[5], shape[6] = shape[2], -shape[1] + 16, shape[3], shape[5], -shape[4] + 16, shape[6]
end
fixShape(shape)
end
workspace:draw()
updateHologram()
end
flipButton.onTouch = function()
local function fix(shape, index)
shape[index] = 16 - shape[index]
shape[index + 3] = 16 - shape[index + 3]
end
for i = 1, #model.shapes do
local shape = model.shapes[i]
if axisComboBox.selectedItem == 1 then
fix(shape, 1)
elseif axisComboBox.selectedItem == 2 then
fix(shape, 2)
else
fix(shape, 3)
end
fixShape(shape)
end
workspace:draw()
updateHologram()
end
disabledListItem.onTouch = function()
updateComboBoxFromModel()
updateWidgetsFromModel()
updateAddRemoveButtonsState()
workspace:draw()
updateHologram()
end
enabledListItem.onTouch = disabledListItem.onTouch
local function addShape()
table.insert(model.shapes, {6, 6, 0, 10, 10, 1, state = modelList.selectedItem == 2 or nil, texture = #textureInput.text > 0 and textureInput.text or nil})
updateComboBoxFromModel()
shapesComboBox.selectedItem = shapesComboBox:count()
updateWidgetsFromModel()
updateAddRemoveButtonsState()
end
local function new()
model = {shapes = {}}
modelList.selectedItem = 1
addShape()
updateSavePath()
end
newItem.onTouch = function()
new()
workspace:draw()
updateHologram()
end
addShapeButton.onTouch = function()
addShape()
workspace:draw()
updateHologram()
end
removeShapeButton.onTouch = function()
table.remove(model.shapes, getSelectedShapeIndex())
updateComboBoxFromModel()
updateWidgetsFromModel()
updateAddRemoveButtonsState()
workspace:draw()
updateHologram()
end
printButton.onTouch = function()
print(model)
end
shapesComboBox.onItemSelected = function()
updateWidgetsFromModel()
workspace:draw()
updateHologram()
end
labelInput.onInputFinished = updateModelFromWidgets
tooltipInput.onInputFinished = updateModelFromWidgets
buttonModeSwitch.onStateChanged = updateModelFromWidgets
collisionSwitch.onStateChanged = updateModelFromWidgets
redstoneSwitch.onStateChanged = updateModelFromWidgets
lightLevelSlider.onValueChanged = updateModelFromWidgets
textureInput.onInputFinished = updateModelFromWidgets
tintSwitch.onStateChanged = updateModelFromWidgets
tintColorSelector.onColorSelected = updateModelFromWidgets
-- Overriding window removing for clearing hologram
local overrideWindowRemove = window.remove
window.remove = function(...)
overrideWindowRemove(...)
if proxies.hologram then
proxies.hologram.clear()
end
end
window.onResize = function(width, height)
window.backgroundPanel.width = width - toolPanel.width
window.backgroundPanel.height = height
viewLayout.width = window.backgroundPanel.width
viewLayout.height = window.backgroundPanel.height
toolPanel.height = height - 3
toolLayout.height = toolPanel.height
printButton.localY = height - 2
end
--------------------------------------------------------------------------------
load(args[1] or (currentScriptDirectory .. "Sample.3dm"))
window:resize(window.width, window.height)
workspace:draw()