From c5f4568cbf8f26ec72ce39e7d0449bec9bde471b Mon Sep 17 00:00:00 2001 From: Quatch Date: Thu, 25 Mar 2021 15:17:25 -0400 Subject: [PATCH 01/12] digshape refactor and gui, from flavourstreet Added flavourstreet's refactor of digshape, and it's new lua gui [keupo's discord, 2021-02-17] [8:10 PM]flavorstreet: Attachment file type: archive digshape-gui-ALPHA.zip 14.32 KB [8:12 PM]flavorstreet: Yall can do whatever u want with it i'll probably never get around to fixing the bugs im so burned out lol --811767111344193566 --- digshape.rb | 787 +++++++++++++++++++++++++++-------------------- gui/digshape.lua | 237 ++++++++++++++ 2 files changed, 691 insertions(+), 333 deletions(-) create mode 100644 gui/digshape.lua diff --git a/digshape.rb b/digshape.rb index c0bc16b..295f826 100644 --- a/digshape.rb +++ b/digshape.rb @@ -47,33 +47,113 @@ TODO: mark origin should not change the digging designation, ellipse cleanup should restore not clear it. =end +DigPos = Struct.new(:x, :y, :z) do + def to_s + return "(#{x},#{y},#{z})" + end + def clone + return DigPos.new(x,y,z) + end +end + +def cursorAsDigPos() + return DigPos.new(df.cursor.x, df.cursor.y, df.cursor.z) +end + +# really nasty hack so that lua can use digshape +$isLuaMode = $script_args[0] == "lua" +$isPreviewOnly = false + +if $isLuaMode then + $script_args.delete_at(0) + $isPreviewOnly = $script_args[0] == "preview" + if $isPreviewOnly then + $script_args.delete_at(0) + end + $output +end + +def writeLuaPos(name, digPos) # pos:::: + if $isLuaMode then + puts "pos:#{name}:#{digPos.to_s}" + end +end + +def setOrigin(x, y, z) # sets the origin and marks if it iff we are in console. cleans up last mark too. + $origin = DigPos.new(x,y,z) + # the rest is just really complicated logic to mark the origin if the user is using the console version + # it also has to play well with lua + + if $oldOrigin then + oldTile = df.map_tile_at($oldOrigin.x, $oldOrigin.y, $oldOrigin.z) + oldTile.dig($oldOriginDesignation) if oldTile.shape_basic == $oldOriginShape && oldTile.designation.dig == digMode2enum('d') + end + if not $isLuaMode then + $oldOrigin = $origin.clone() + newOriginTile = df.map_tile_at($origin.x, $origin.y, $origin.z) + $oldOriginDesignation = newOriginTile.designation.dig + $oldOriginShape = newOriginTile.shape_basic + + digAt($origin.x, $origin.y, $origin.z, 'd', buffer: false) + else + $oldOrigin = nil # don't undo our origins if we are in lua mode + end +end + +def setMajor(x, y, z) + $major = DigPos.new(x,y,z) +end + +def stdout(msg) + if $isLuaMode == false then + puts msg + else + puts "msg:"+msg + end +end -def markOrigin(ox, oy, oz) - t = df.map_tile_at(ox, oy, oz) - if t then - s = t.shape_basic - #TODO: preseve designation: - #$originTile = t.designation # a global to store the original origin state - #puts "origin: #{$originTile}" - t.dig(:Default) if s == :Wall +def stderr(msg) # write an error mesage + if $isLuaMode == false then + puts " Error: "+msg + else + puts "err:"+msg end end +def scriptError(msg) # call this when you reach corner cases / the fault perhaps isn't the user + stderr(msg) + raise "oopsie! script errored! ;)" +end + +def userSucks(msg) # call this when we don't like the user's input + stderr(msg) + throw :script_finished +end + + -def unDig() - #Exicute one level of undo. +def undo() # BUG! Does not keep track of Z levels + #Execute one level of undo. #z level is presumed to be the current. + # todo, have multiple levels of undo / redo i=$digBufferX.length - while i >= 0 do + newBufferX = [] + newBufferY = [] + newBufferZ = [] # redundant, i.e. always the same most of the time, but needed so that we use it as a pointer for digAt. Also supports digging multi dimenisional shapes + newBufferD = [] + while i > 0 do x=$digBufferX.pop y=$digBufferY.pop + z=$digBufferZ.pop d=$digBufferD.pop - - digAt(x,y,df.cursor.z, enum2dig(d), buffer=false) + digAt(x,y,z, enum2digMode(d), buffer: true, bufferX: newBufferX, bufferY: newBufferY, bufferZ: newBufferZ, bufferD: newBufferD) i = i-1 - end - #clear buffer for next dig. - clearDigBuffer() + end + #clear buffer for next dig + $digBufferX = newBufferX + $digBufferY = newBufferY + $digBufferZ = newBufferZ + $digBufferD = newBufferD end @@ -81,40 +161,83 @@ def clearDigBuffer() #clear buffer for next dig, or initialize it's existance on first run. $digBufferX=[] $digBufferY=[] + $digBufferZ=[] $digBufferD=[] end +def digMode2enum(digMode) + #this function turns a digmode into the appropriate enum for easier comparison on tile reading (eg floodfill.) + case digMode #from https://github.com/DFHack/scripts/blob/master/digfort.rb + when 'd'; return :Default + when 'u'; return :UpStair + when 'j'; return :DownStair + when 'i'; return :UpDownStair + when 'h'; return :Channel + when 'r'; return :Ramp + when 'x'; return :No + else + scriptError("Unknown digMode, `"+digMode+"', digMode must be any of 'd', 'u', 'j', 'i', 'h', 'r', or 'x', which correspond to the designation keys") + end +end -def digAt(x, y, z, digMode = 'd', buffer=true) - t = df.map_tile_at(x, y, z) - - #store the current tile's designation in a undo buffer - if buffer then - $digBufferX.push(x) - $digBufferY.push(y) - $digBufferD.push(t.designation.dig) + +def enum2digMode(digEnum) + #this function turns a designation enum into the appropriate digtype character + case digEnum #from https://github.com/DFHack/scripts/blob/master/digfort.rb + when :Default; return 'd' + when :UpStair; return 'u' + when :DownStair; return 'j' + when :UpDownStair; return 'i' + when :Channel; return 'h' + when :Ramp; return 'r' + when :No; return 'x' + else + scriptError("Unknown digEnum `#{digEnum.to_s}'") end +end + +def isDigPermitted(digMode, tileShape) + # can we dig on this tile? + + if not tileShape then return false end + case digMode + when 'd', 'u', 'i', 'r'; return tileShape == :Wall + when 'j', 'h'; return tileShape == :Wall || tileShape == :Floor + when 'x'; return true + else + scriptError("Unknown digMode: `"+digMode+"'") + end +end + +def digAt(x, y, z, digMode = 'd', buffer: true, bufferX: $digBufferX, bufferY: $digBufferY, bufferZ: $digBufferZ, bufferD: $digBufferD) + tile = df.map_tile_at(x, y, z) # check if the tile returned is valid, ignore if its not (out of bounds, air, etc) - if t then - s = t.shape_basic - - case digMode #from https://github.com/DFHack/scripts/blob/master/digfort.rb - when 'd'; t.dig(:Default) if s == :Wall - when 'u'; t.dig(:UpStair) if s == :Wall - when 'j'; t.dig(:DownStair) if s == :Wall or s == :Floor - when 'i'; t.dig(:UpDownStair) if s == :Wall - when 'h'; t.dig(:Channel) if s == :Wall or s == :Floor - when 'r'; t.dig(:Ramp) if s == :Wall - when 'x'; t.dig(:No) + if tile then + tileShape = tile.shape_basic + + if isDigPermitted(digMode, tileShape) then + if $isPreviewOnly then + puts "dig:"+digMode+":"+x.to_s+":"+y.to_s+":"+z.to_s else - puts " Error: Unknown digtype" - throw :script_finished + if buffer then # store the current tile's designation in a undo buffer + bufferX.push(x) + bufferY.push(y) + bufferZ.push(z) + bufferD.push(tile.designation.dig) + + end + tile.dig(digMode2enum(digMode)) + end end end end + + + + # https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm def drawLineLow(x0, y0, z0, x1, y1, z1, digMode = 'd') dx = x1 - x0 @@ -179,7 +302,6 @@ def drawLine(x0, y0, z0, x1, y1, z1, digMode = 'd') end end - def plotQuadRationalBezierSeg(x0, y0, z0, x1, y1, z1, x2, y2, z2, w, digMode = 'd') #/* plot a limited rational Bezier segment, squared weight */ #http://members.chello.at/easyfilter/bresenham.pdf listing 12 @@ -296,8 +418,6 @@ def plotQuadRationalBezierSeg(x0, y0, z0, x1, y1, z1, x2, y2, z2, w, digMode = ' drawLine(x0,y0,z0, x2,y2,z0, digMode) end - - def plotQuadRationalBezier(x0, y0, z0, x1, y1, z1, x2, y2, z2, w=1.5, digMode = 'd') #http://members.chello.at/easyfilter/bresenham.pdf listing 11 ## plot any quadratic rational Bezier curve */ @@ -386,7 +506,6 @@ def plotQuadRationalBezier(x0, y0, z0, x1, y1, z1, x2, y2, z2, w=1.5, digMode plotQuadRationalBezierSeg(x0, y0, z0, x1, y1, z0, x2, y2, z0, w * w, digMode) end - def plotRotatedEllipse(x, y, z, a, b, angle, digMode='d') ## plot ellipse rotated by angle (radian) */ #taken from: http://members.chello.at/easyfilter/bresenham.pdf listing 13. Explicitly released without copyright @@ -421,8 +540,6 @@ def plotRotatedEllipse(x, y, z, a, b, angle, digMode='d') plotRotatedEllipseRect(x - a, y - b, z, x + a, y + b, (4 * zd * Math.cos(angle)), digMode) end - - def plotRotatedEllipseRect(x0, y0, z0, x1, y1, zd, digMode='d') #http://members.chello.at/easyfilter/bresenham.pdf listing 13 #/* rectangle enclosing the ellipse, integer rotation angle */ @@ -437,7 +554,7 @@ def plotRotatedEllipseRect(x0, y0, z0, x1, y1, zd, digMode='d') if (zd == 0) #Special case: no rotation. Use standard method. /* looks nicer */ #this should never be reached, as we call this from the regular ellipse function. - puts "zd=0 degenerate case" + stdout "zd=0 degenerate case" drawEllipse(x0,y0,z0, x1,y1,z0) return end @@ -445,12 +562,11 @@ def plotRotatedEllipseRect(x0, y0, z0, x1, y1, zd, digMode='d') ## squared weight of P1 */ if (w != 0.0) then w = (w - zd) / (w + w) - end + end if not(w <= 1.0 && w >= 0.0) then #/* limit angle to |zd|<=xd*yd */ - puts " Error: Limit angle to |zd|<=xd*yd" - throw :script_finished - end + scriptError "Limit angle to |zd|<=xd*yd" + end ## snap xe,ye to int */ xd = (xd * w + 0.5).floor @@ -463,7 +579,6 @@ def plotRotatedEllipseRect(x0, y0, z0, x1, y1, zd, digMode='d') plotQuadRationalBezierSeg(x1, y1-yd, z0, x1,y0, z0, x0+xd,y0, z0, w, digMode) end - def drawEllipse(x0, y0, z0, x1, y1, z1, x2=nil, y2=nil, z2=nil, filled = false, digMode = 'd', mode = 'bbox') # A Fast Bresenham Type Algorithm For Drawing Ellipses http://homepage.smc.edu/kennedy_john/belipse.pdf (https://www.dropbox.com/s/3q89g566u115g3q/belipse.pdf?dl=0) # also adapted from https://github.com/teichgraf/WriteableBitmapEx/blob/master/Source/WriteableBitmapEx/WriteableBitmapShapeExtensions.cs used under the MIT license @@ -721,57 +836,7 @@ def drawStar(x0, y0, z0, x1, y1, z1, n = 5, skip = 2, digMode = 'd') end end -def dig2enum(digMode) - #this function turns a digmode into the appropriate enum for easier comparison on tile reading (eg floodfill.) - case digMode #from https://github.com/DFHack/scripts/blob/master/digfort.rb - when 'd'; return :Default - when 'u'; return :UpStair - when 'j'; return :DownStair - when 'i'; return :UpDownStair - when 'h'; return :Channel - when 'r'; return :Ramp - when 'x'; return :No - else - puts " Error: Unknown digtype" - throw :script_finished - end -end - -def enum2dig(digEnum) - #this function turns a designation enum into the appropriate digtype character - case digEnum #from https://github.com/DFHack/scripts/blob/master/digfort.rb - when :Default; return 'd' - when :UpStair; return 'u' - when :DownStair; return 'j' - when :UpDownStair; return 'i' - when :Channel; return 'h' - when :Ramp; return 'r' - when :No; return 'x' - else - puts " Error: Unknown digEnum" - throw :script_finished - end -end - -def digPermitted(digMode, tileShape_basic) - #can we dig on this tile? - - if not tileShape_basic then return false end - - case digMode - when 'd'; if tileShape_basic == :Wall then return true else return false end - when 'u'; if tileShape_basic == :Wall then return true else return false end - when 'j'; if tileShape_basic == :Wall || tileShape_basic == :Floor then return true else return false end - when 'i'; if tileShape_basic == :Wall then return true else return false end - when 'h'; if tileShape_basic == :Wall || tileShape_basic == :Floor then return true else return false end - when 'r'; if tileShape_basic == :Wall then return true else return false end - when 'x'; return true - else - puts " Error: Unknown digtype" - throw :script_finished - end - end def floodfill(x,y,z,targetDig, digMode, maxCounter= 10000) #targetDig: what designation type can we overwrite? @@ -783,14 +848,16 @@ def floodfill(x,y,z,targetDig, digMode, maxCounter= 10000) if not t then #ignore impossible tiles (eg air.) + stdout "Tile does not exist" throw :script_finished return end - digNum = dig2enum(digMode) #Stash this, we'll use it many times. + digNum = digMode2enum(digMode) #Stash this, we'll use it many times. if t.designation.dig == digNum then #don't dig tiles that are already dug + stdout "Tile is already dug" throw :script_finished return end @@ -808,7 +875,7 @@ def floodfill(x,y,z,targetDig, digMode, maxCounter= 10000) xi = xw - 1 #move xw cursor west until it hits a match t=df.map_tile_at(xi,y,z) - if !t || xi == 0 || t.designation.dig != targetDig || !digPermitted(digMode,t.shape_basic) then + if !t || xi == 0 || t.designation.dig != targetDig || !isDigPermitted(digMode,t.shape_basic) then break end xw = xi @@ -819,7 +886,7 @@ def floodfill(x,y,z,targetDig, digMode, maxCounter= 10000) xi = xe + 1 #move xe cursor east until it hits a match t=df.map_tile_at(xi,y,z) - if !t || xi == 0 || t.designation.dig != targetDig || !digPermitted(digMode,t.shape_basic) then + if !t || xi == 0 || t.designation.dig != targetDig || !isDigPermitted(digMode,t.shape_basic) then break end xe = xi @@ -831,18 +898,20 @@ def floodfill(x,y,z,targetDig, digMode, maxCounter= 10000) counter = counter -1 if counter <=0 then - puts " Max coverage of #{maxCounter} tiles reached. Use multiple floods, or add a number for max coverage as 'digshape flood [max coverage] [dig type]'." + stdout " Max coverage of #{maxCounter} tiles reached. Use multiple floods, or add a number for max coverage as 'digshape flood [max coverage] [dig type]'." + stdout " Automatically cancelling flood" + undo() return end #check N/S t = df.map_tile_at(xi,y+1,z) - if t && t.designation.dig == targetDig && digPermitted(digMode,t.shape_basic) then + if t && t.designation.dig == targetDig && isDigPermitted(digMode,t.shape_basic) then xStack.push(xi) yStack.push(y+1) end t = df.map_tile_at(xi,y-1,z) - if t && t.designation.dig == targetDig && digPermitted(digMode,t.shape_basic) then + if t && t.designation.dig == targetDig && isDigPermitted(digMode,t.shape_basic) then xStack.push(xi) yStack.push(y-1) end @@ -857,266 +926,318 @@ def floodfill(x,y,z,targetDig, digMode, maxCounter= 10000) # script execution start if not $script_args[0] or $script_args[0]=="help" or $script_args[0]=="?" then - puts " To draw downstair: digshape downstair depth" - puts " To set origin: digshape origin" - puts " To draw line after origin is set: digshape line" - puts " To draw ellipse after origin is set (as bounding box): digshape ellipse " - puts "..To draw an ellipse after origin is set (by major and minor axis): digshape major (must be horizontal or vertical), then digshape ellipse3p" - puts " To draw a 3 point bezier curve after origin and major are set: digshape bez " - puts "..To draw a circle after origin is set, select any point as a diameter: digshape circle2p " - puts " To draw a polygon after origin is set (as center) with the cursor as a vertex: digshape polygon <# sides>" - puts " To draw a polygon after origin is set (as center) with the cursor as a midpoint of a segment(apothem): digshape polygon <# sides> apothem" - puts " To draw a star after origin is set (as center) with the cursor as a vertex : digshape star <# points> " - puts " To flood fill with a designation, overwriting ONLY the designation under the cursor (warning: slow on areas bigger than 10k tiles..): digshape flood [maxArea=10000]" - puts " To undo the previous command (restoring designation): digshape undo" - puts " To move all markers to the current z level (without displaying them): digshape resetz" - puts " All commands accept a one letter digging designation [dujihrx] at the end, or will default to 'd'" + stdout " To draw downstair: digshape downstair depth" + stdout " To set origin: digshape origin" + stdout " To draw line after origin is set: digshape line" + stdout " To draw ellipse after origin is set (as bounding box): digshape ellipse " + stdout "..To draw an ellipse after origin is set (by major and minor axis): digshape major (must be horizontal or vertical), then digshape ellipse3p" + stdout " To draw a 3 point bezier curve after origin and major are set: digshape bez " + stdout "..To draw a circle after origin is set, select any point as a diameter: digshape circle2p " + stdout " To draw a polygon after origin is set (as center) with the cursor as a vertex: digshape polygon <# sides>" + stdout " To draw a polygon after origin is set (as center) with the cursor as a midpoint of a segment(apothem): digshape polygon <# sides> apothem" + stdout " To draw a star after origin is set (as center) with the cursor as a vertex : digshape star <# points> " + stdout " To flood fill with a designation, overwriting ONLY the designation under the cursor (warning: slow on areas bigger than 10k tiles..): digshape flood [maxArea=10000]" + stdout " To undo the previous command (restoring designation): digshape undo" + stdout " To move all markers to the current z level (without displaying them): digshape resetz" + stdout " All commands accept a one letter digging designation [dujihrx] at the end, or will default to 'd'" throw :script_finished end command = $script_args[0] -argument1 = $script_args[1] -argument2 = $script_args[2] -argument3 = $script_args[3] +$script_args.delete_at(0) if df.cursor.x == -30000 then - puts " Error: cursor must be on map" - throw :script_finished + userSucks "Cursor must be on map" end -if command=="o" or command=="set" then #alias - command="origin" -elsif command=="keupo" or command=="stairs" or command=="downstairs" then - command="downstair" +if not (command == 'undo' or command=='u') and not $isPreviewOnly then + clearDigBuffer() #clear the dig buffer so we can undo the following command. Or initialize it's first run end -if command != 'undo' then - clearDigBuffer() #clear the dig buffer so we can undo the following command. Or initialize it's first run +def requireOriginZLevel(msg: "Origin and target must be on the same z-level (use command 'digshape resetz' or 'digshape setz [Z-level, default=Cursor Z]' to fix)") + if df.cursor.z != $origin.z then + userSucks(msg) + end + writeLuaPos("origin",$origin) # visualize them for the user end -case command - when 'origin' - $originx = df.cursor.x - $originy = df.cursor.y - $originz = df.cursor.z - - markOrigin($originx, $originy, $originz) - when 'resetz' - $originz = $majorz = df.cursor.z - when 'line' - dig = getDigMode(argument1) - - if df.cursor.z == $originz then - drawLine($originx, $originy, $originz, df.cursor.x, df.cursor.y, df.cursor.z, dig) +def requireMajor(msg: "Set a point for the end of the major axis with the cursor and 'digshape major'") + if $major == nil then + userSucks(msg) + end + requireOriginZLevel() + writeLuaPos("origin", $origin) # visualize them for the user + writeLuaPos("major", $major) # visualize them for the user +end + +def getDigModeArgument(args) + argument = args[0] + digMode = getDigMode(argument) + args.delete_at(0) + + return digMode +end + +def getFilledArgument(args, default: false) # this doesn't *expect* and argument and so only consumes an argument when something matches + argument = args[0] + case argument + when 'filled', 'f', 'true', 'yes', 'y'; filled = true + when 'hollow', 'h', 'false', 'no', 'n'; filled = false else - puts " Error: origin and target must be on the same z level" - throw :script_finished - end - when 'ellipse' #digshape ellipse [filled] [digmode] - filled = false - case argument1 - when 'filled'; filled = true - when 'hollow'; filled = false - when 'true'; filled = true - when 'false'; filled = false - when 't'; filled = true - when 'f'; filled = false - when 'y'; filled = true - when 'n'; filled = false - end + return default # doesn't consume if nothing matches + end + args.delete_at(0); + return filled +end - dig = getDigMode(argument1) #check argument 1 for dig instructions - if argument2 then # if argument 2 is present, look at that for dig instructions - dig = getDigMode(argument2) - end +def getFloatArgument(args, default: nil, type: "(unnamed number)", positive: true) + num = args[0] + result = nil + defaultMessage = "" - if df.cursor.z == $originz then - drawEllipse($originx, $originy, $originz, df.cursor.x, df.cursor.y, df.cursor.z, x2=nil, y2=nil, z2=nil, filled=filled, digMode=dig, mode = 'bbox') + if default != nil then + defaultMessage = "Use `-' for the default value (#{default})" + end - # remove origin designation - digAt($originx, $originy, $originz, 'x') + if not num then + userSucks("Must supply #{type} parameter (number).#{defaultMessage}") + end + args.delete_at(0) + + case num + when 'default','-'; + userSucks("No default value for #{type} parameter!") if default == nil + result = default else - puts " Error: origin and target must be on the same z level" - throw :script_finished - end - when 'circle2p' #digshape circle2p [filled] [digmode] - filled = false - case argument1 - when 'filled'; filled = true - when 'hollow'; filled = false - when 'true'; filled = true - when 'false'; filled = false - when 't'; filled = true - when 'f'; filled = false - when 'y'; filled = true - when 'n'; filled = false - end - - dig = getDigMode(argument1) #check argument 1 for dig instructions - if argument2 then # if argument 2 is present, look at that for dig instructions - dig = getDigMode(argument2) - end + result = Float(num) rescue userSucks("Malformed number for "+type+" parameter, got `"+num+"'.#{defaultMessage}") + end + + if positive && result<0 then + userSucks("Expected positive number for #{type} parameter, got `#{num}'.#{defaultMessage}") + end + + return result +end + +def getIntegerArgument(args, default: nil, type: "(unnamed integer)", positive: true) + num = args[0] + result = nil + defaultMessage = "" - if df.cursor.z == $originz then - drawEllipse($originx, $originy, $originz, df.cursor.x, df.cursor.y, df.cursor.z, x2=nil, y2=nil, z2=nil, filled=filled, digMode = dig, mode = 'diameter') + if default != nil then + defaultMessage = "Use `-' for the default value (#{default})" + end + + if not num then + userSucks("Must supply #{type} parameter (integer).#{defaultMessage}") + end + args.delete_at(0) + + case num + when 'default','-'; + userSucks("No default value for #{type} parameter!") if default == nil + result = default else - puts " Error: origin and target must be on the same z level" - throw :script_finished + result = Integer(num) rescue userSucks("Malformed integer for "+type+" parameter, got `"+num+"'.#{defaultMessage}") + end + + if positive && result<0 then + userSucks("Expected positive integer for #{type} parameter, got `#{num}'.#{defaultMessage}") + end + + return result +end + +def makeDefaultPosMap(oldPos) + return { + '~' => cursorAsDigPos(), # cursor positon + '-' => oldPos.clone() # old value of this (i.e. leave it unchanged) + } +end + +def getPosComponentArgument(args, defaultMap, symbol) + if defaultMap[args[0]] then # it is either ~ or - + value = defaultMap[args[0]][symbol] + args.delete_at(0) + return value + else # it is an integer + return getIntegerArgument(args, type: "#{symbol.to_s} coordinate", positive: true) + end +end + +def getPositionArgument(args, oldPos, default: nil) # X Y Z, ~ ~ ~, - - -, or any mix # returns nil if no default! + if args[0] && args[1] && args[2] then + defaultMap = makeDefaultPosMap(oldPos) + return DigPos.new(getPosComponentArgument(args, defaultMap, :x), + getPosComponentArgument(args, defaultMap, :y), + getPosComponentArgument(args, defaultMap, :z)) + else + return default + end +end + +def noMoreArguments(args) + if args[0] then + userSucks("Did not expect more arguments #{args}") + end +end + +#def registerCommand(name, aliases, usage) +# return "TODO:" +#end + +case command + when 'origin', 'o', 'set' + # $usage = createUsage(name: 'origin', aliases: ['o', 'set']) # TODO ADD USAGES TO EACH COMMANDS, make scriptError/userSucks print them out + # Even better, to refator all these commands with associated data into classes / anonoymous functions so we can eventually do digshape help + newOrigin = getPositionArgument($script_args, $origin, default: cursorAsDigPos()) + + noMoreArguments($script_args) + + setOrigin(newOrigin.x, newOrigin.y, newOrigin.z) # need to refactor setOrigin + + writeLuaPos("origin", $origin) + + when 'major', 'm' #used to mark the end point of the major diameter + newMajor = getPositionArgument($script_args, $major, default: cursorAsDigPos()) + noMoreArguments($script_args) + + requireOriginZLevel() + + setMajor(newMajor.x, newMajor.y, newMajor.z) # need to refactor setMajor + + writeLuaPos("major", $major) + + + + stdout "Now move the cursor to the minor axis radius (extent) and call ellipse3p" + when 'resetz', 'setz' + z = df.cursor.z # default + + if args[0] then + z = getPosComponentArgument(args, makeDefaultPosMap(origin), :z) # only really need the z-component from origin for default end - when 'major' #digshape major - #used to mark the end point of the major diameter - #$major = df.cursor - $majorx = df.cursor.x - $majory = df.cursor.y - $majorz = df.cursor.z - if df.cursor.z == $originz then - markOrigin($majorx, $majory, $majorz) - puts " Now move the cursor to the minor axis radius (extent) and call ellipse3p" - else - puts " Error: origin and target must be on the same z level" - throw :script_finished + noMoreArguments($script_args) + + setOrigin($origin.x, $origin.y, z) + if $major then + setMajor($major.x, $major.y, z) end - when 'ellipse3p' #digshape ellipse3p [filled] [digmode] - if df.cursor.z == $originz then - if $majorx == nil then - puts " Error: Set a point for the end of the major axis with the cursor and 'digshape major'" - throw :script_finished - end - - filled = false - case argument1 - when 'filled'; filled = true - when 'hollow'; filled = false - when 'true'; filled = true - when 'false'; filled = false - when 't'; filled = true - when 'f'; filled = false - when 'y'; filled = true - when 'n'; filled = false - end - - if filled then - puts " Filled not yet supported for 3p ellipses." - filled = false - end + when 'ls', 'status' + stdout "origin: #{$origin != nil ? $origin.to_s : ''}" + stdout "major : #{$major != nil ? $major.to_s : ''}" + stdout "cursor: #{cursorAsDigPos().to_s}" + + when 'line', 'l' + digMode = getDigModeArgument($script_args) + noMoreArguments($script_args) - dig = getDigMode(argument1) #check argument 1 for dig instructions - if argument2 then # if argument 2 is present, look at that for dig instructions - dig = getDigMode(argument2) - end + requireOriginZLevel() - drawEllipse($originx, $originy, $originz, $majorx, $majory, $majorz, df.cursor.x, df.cursor.y, df.cursor.z, filled = filled, digMode = dig, mode = 'axis') - else - puts " Error: all control points must be on the same z level" - throw :script_finished + drawLine($origin.x, $origin.y, $origin.z, df.cursor.x, df.cursor.y, df.cursor.z, digMode) + + when 'ellipse', 'e' #digshape ellipse [filled] [digmode] + filled = getFilledArgument($scripts_args) + digMode = getDigModeArgument($script_args) + noMoreArguments($script_args) + + requireOriginZLevel() + + drawEllipse($origin.x, $origin.y, $origin.z, df.cursor.x, df.cursor.y, df.cursor.z, x2=nil, y2=nil, z2=nil, filled=filled, digMode=digMode, mode='bbox') # fixme: default arguments should be colon not equals + + when 'circle2p', 'circle', 'c' #digshape circle2p [filled] [digmode] + filled = getFilledArgument($script_args) + digMode = getDigModeArgument($script_args) #check argument 1 for dig instructions + noMoreArguments($script_args) + + requireOriginZLevel() + + drawEllipse($origin.x, $origin.y, $origin.z, df.cursor.x, df.cursor.y, df.cursor.z, x2=nil, y2=nil, z2=nil, filled=filled, digMode=digMode, mode='diameter') # fixme: default arguments should be colon not equals + + when 'ellipse3p', 'e3p' #digshape ellipse3p [filled] [digmode] + filled = getFilledArgument($script_args) + digMode = getDigModeArgument($script_args) + noMoreArguments($script_args) + + requireOriginZLevel(msg:"All control points must be on the same z level") + requireMajor() + + if filled then + stdout "Filled not yet supported for 3p ellipses." + filled = false end - when 'bez' #digshape bez + + drawEllipse($origin.x, $origin.y, $origin.z, $major.x, $major.y, $major.z, df.cursor.x, df.cursor.y, df.cursor.z, filled=filled, digMode=digMode, mode='axis') # fixme: default arguments should be colon not equals + + when 'bezier', 'bez', 'b' #digshape bezier [weight] digmode] #use origin and major as endpoints, cursor as curve shaper - if df.cursor.z == $originz then - if $majorx == nil then - puts " Error: Set an endpoint for the curve with the cursor and 'digshape major'" - throw :script_finished - end - - weight = argument1.to_f - if weight = 0 then - weight = 1.5 - end - - dig = getDigMode(argument1) #check argument 1 for dig instructions - if argument2 then # if argument 2 is present, look at that for dig instructions - dig = getDigMode(argument2) - end - - plotQuadRationalBezier($originx, $originy, $originz, df.cursor.x, df.cursor.y, df.cursor.z, $majorx, $majory, $majorz, weight, dig) - else - puts " Error: all control points must be on the same z level" - throw :script_finished - end - when 'polygon' - if not argument1 then - puts " Must supply a polygon n-sides parameter" - throw :script_finished - else - n = argument1.to_i - dig = getDigMode(argument2) - if argument3 then - dig = getDigMode(argument3) - end - apothem=false; - case argument2 - when 'apothem'; apothem=true - when 'radius'; apothem=false - when 'a'; apothem=true - when 'r'; apothem=false - when 't'; apothem=true - when 'f'; apothem=false - when 'y'; apothem=true - when 'n'; apothem=false - end - if df.cursor.z == $originz then - drawPolygon($originx, $originy, $originz, df.cursor.x, df.cursor.y, df.cursor.z, n, apothem, dig) - else - puts " Error: origin and target must be on the same z level" - throw :script_finished - end - end - when 'star' # star N [SKIP=2] [DIGMODE] - if not argument1 then - puts " Must supply a star n-sides parameter" - throw :script_finished - else - dig = getDigMode(argument2) - if argument3 then - dig = getDigMode(argument3) - end - n = argument1.to_i - skip = Integer(argument2) rescue 2 + weight = getFloatArgument($script_args, default: 1.5, type: "bezier weight") + digMode = getDigModeArgument($script_args) #check argument 1 for dig instructions + noMoreArguments($script_args) + + requireOriginZLevel(msg:"All control points must be on the same z level") + requireMajor() + + plotQuadRationalBezier($origin.x, $origin.y, $origin.z, df.cursor.x, df.cursor.y, df.cursor.z, $major.x, $major.y, $major.z, weight, digMode) - if df.cursor.z == $originz then - drawStar($originx, $originy, $originz, df.cursor.x, df.cursor.y, df.cursor.z, n, skip, dig) - else - puts " Error: origin and target must be on the same z level" - throw :script_finished - end + when 'polygon', 'p' #digshape polygon [sides] [apothem/radius] [digMode] # apothem is default + sides = getIntegerArgument($script_args, type: "polygon n-sides") + + apothem = false # custom argument parse + case $script_args[0] + when 'apothem', 'a', 't', 'true', 'y', 'yes'; apothem=true; $script_args.delete_at(0) + when 'radius', 'r', 'f', 'false', 'n', 'no'; apothem=false; $script_args.delete_at(0) end - when 'downstair' - if not argument1 then - puts " Must supply a depth parameter" - throw :script_finished - else - depth = argument1.to_i - if depth <= 0 then - puts " Depth must be an integer greater than zero" - throw :script_finished - else - digKeupoStair(df.cursor.x, df.cursor.y, df.cursor.z, depth) - end + + digMode = getDigModeArgument($script_args) + + noMoreArguments($script_args) + + requireOriginZLevel() + + drawPolygon($origin.x, $origin.y, $origin.z, df.cursor.x, df.cursor.y, df.cursor.z, sides, apothem, digMode) + + when 'star', 's' #digshape star N [skip=2] [digMode] + n = getIntegerArgument($script_args, type: "star n-sides") + + skip = getIntegerArgument($script_args, default: 2, type: "skip") + digMode = getDigModeArgument($script_args) + + noMoreArguments($script_args) + + requireOriginZLevel() + + drawStar($origin.x, $origin.y, $origin.z, df.cursor.x, df.cursor.y, df.cursor.z, n, skip, digMode) + + when 'keupo', 'stairs', 'downstairs', 'downstair' #digshape keupo depth + depth = getIntegerArgument($script_args, type: "depth") + + noMoreArguments($script_args) + + if depth <= 0 then + userSucks "Depth must be an integer greater than zero" end - when 'flood' - maxArea = argument1.to_i - if maxArea == 0 then maxArea = 10000 end - dig = getDigMode(argument1) #check argument 1 for dig instructions - if argument2 then # if argument 2 is present, look at that for dig instructions - dig = getDigMode(argument2) - end + digKeupoStair(df.cursor.x, df.cursor.y, df.cursor.z, depth) + when 'flood', 'f' + maxArea = getIntegerArgument($script_args, default: 10000, type: "maximum flood area") + digMode = getDigModeArgument($script_args) + + noMoreArguments($script_args) - t=df.map_tile_at(df.cursor.x, df.cursor.y, df.cursor.z) - targetDig = t.designation.dig #we will only fill the designation type under the cursor. + tile = df.map_tile_at(df.cursor.x, df.cursor.y, df.cursor.z) + targetDig = tile.designation.dig #we will only fill the designation type under the cursor. - if not t.designation.dig==:No then - if dig2enum(dig) == t.designation.dig then - puts " Error: floodfill must be centered on an undesignated/matching tile." - throw :script_finished + if targetDig != :No then + if targetDig == digMode2enum(digMode) then + userSucks "Floodfill must be centered on an undesignated/matching tile." end end - floodfill(df.cursor.x, df.cursor.y, df.cursor.z, targetDig, dig, maxArea) - throw :script_finished - when 'undo' - unDig() + floodfill(df.cursor.x, df.cursor.y, df.cursor.z, targetDig, digMode, maxArea) + when 'undo', 'u' + undo() + else - puts " Error: Invalid command" - throw :script_finished -end + userSucks "Invalid command" +end \ No newline at end of file diff --git a/gui/digshape.lua b/gui/digshape.lua new file mode 100644 index 0000000..ce07a7b --- /dev/null +++ b/gui/digshape.lua @@ -0,0 +1,237 @@ +--designating tool + +--[====[ + +gui/digshape +=========== +gui front-end for digshape.rb + +]====] + +local utils = require "utils" +local gui = require "gui" +local guidm = require "gui.dwarfmode" +local dialog = require "gui.dialogs" + +DigshapeUI = defclass(DigshapeUI, guidm.MenuOverlay) + +DigshapeUI.ATTRS { + state = "preview", + activeDesgination = 'd', + currentCommand = 'circle hollow @', + currentOutput = {}, + currentError = {}, + currentDig = {} + -- default properties for self here +} + + +local digButtons={ + {key='d', symbol=" ", text="Mine"}, + {key='i', symbol="X", text="U/D Stair"}, + {key='h', symbol="_", text="Channel"}, + {key='r', symbol=30, text="Up Ramp"}, + {key='j', symbol=">", text="Down Stair"}, + {key='u', symbol="<", text="Up Stair"}, + {key='x', symbol=" ", text="Remove Designation"}, +} + +local digModeToButton = {} +for _, data in pairs(digButtons) do + data.keybind = ("CUSTOM_%s"):format(data.key:upper()) + digModeToButton[data.key] = data +end + +local buttons = { + {key="p", text="Set digshape command", callback=function(self) + dialog.showInputPrompt("Set digshape command", "Enter a digshape command", COLOR_WHITE, self.currentCommand, function(result) + self.currentCommand=result + self:runCurrentCommand(true) + end) + end}, + {key="o", text="Set origin", callback=function(self) + dfhack.run_command_silent("digshape lua origin") + self:runCurrentCommand(true) + end}, + {key="m", text="Set major", callback=function(self) + dfhack.run_command_silent("digshape lua major") + self:runCurrentCommand(true) + end}, + {key="SELECT", keybind="SELECT", text="Execute command", callback=function(self) + self:runCurrentCommand(false) + end}, + {key="z", text="Undo digshape command", callback=function(self) + dfhack.run_command("digshape undo") + end} +} +for _, data in pairs(buttons) do + data.keybind = data.keybind or ("CUSTOM_%s"):format(data.key:upper()) +end + +local lastX = df.global.cursor.x +local lastY = df.global.cursor.y +local lastZ = df.global.cursor.z + +function DigshapeUI:runCurrentCommand(preview) + local command = ("digshape lua %s%s"):format(preview and "preview " or "", self.currentCommand):gsub("@", self.activeDesgination) + --print(("command='%s'"):format(command)) + local output = dfhack.run_command_silent(command) + self.currentOutput = {} + self.currentError = {} + self.currentDig = {} + self.origin = nil + self.major = nil + --print("output=", output) + for line in output:gmatch("[^\r\n]+") do + messageType = line:match("^([^:]+):") + if messageType == "msg" then + messageContents = line:match("^msg:(.*)$") + table.insert(self.currentOutput, messageContents) + elseif messageType == "err" then + messageContents = line:match("^err:(.*)$") + table.insert(self.currentError, messageContents) + elseif messageType == "dig" then + digMode, x, y, z = line:match("^dig:([^:]+):([^:]+):([^:]+):([^:]+)") + table.insert(self.currentDig, {digMode=digMode, x=tonumber(x), y=tonumber(y), z=tonumber(z), symbol=digModeToButton[digMode].symbol}) + elseif messageType == "pos" then + posname, x, y, z = line:match("^pos:([^:]+):%(([^,]+),([^,]+),([^,]+)%)") + x, y, z = tonumber(x), tonumber(y), tonumber(z) + if posname == "origin" then + self.origin = xyz2pos(x, y, z) + elseif posname == "major" then + self.major = xyz2pos(x, y, z) + end + + else + print("unhandled output:", line) + end + end +end + +function DigshapeUI:init() + self.saved_mode = df.global.ui.main.mode + df.global.ui.main.mode=df.ui_sidebar_mode.LookAround + self:runCurrentCommand(true) +end + +function DigshapeUI:onDestroy() + df.global.ui.main.mode = self.saved_mode +end + +local function paintMapTile(dc, vp, cursor, pos, ...) + if not same_xyz(cursor, pos) then + local stile = vp:tileToScreen(pos) + if stile.z == 0 then -- FIXME: reduce lag by increasing overlay + dc:map(true):seek(stile.x,stile.y):char(...):map(false) + end + end +end + + +function DigshapeUI:renderOverlay() + local vp=self:getViewport() + local dc = gui.Painter.new(self.df_layout.map) + local visible = gui.blink_visible(500) + + local cursorX, cursorY, cursorZ = df.global.cursor.x, df.global.cursor.y, df.global.cursor.z + if lastX ~= cursorX or lastY ~= cursorY or lastZ ~= cursorZ then + lastX, lastY, lastZ = cursorX, cursorY, cursorZ + self:runCurrentCommand(true) + end + + for _, dig in ipairs(self.currentDig) do + paintMapTile(dc, vp, df.global.cursor, xyz2pos(dig.x, dig.y, dig.z), dig.symbol, COLOR_BLACK, self.activeDesgination=='x' and COLOR_RED or COLOR_BROWN) + end + + if self.origin then + paintMapTile(dc, vp, df.global.cursor, self.origin, '+', COLOR_YELLOW) + end + if self.major then + paintMapTile(dc, vp, df.global.cursor, self.major, '+', COLOR_LIGHTGREEN) + end + + +end + +function DigshapeUI:onRenderBody(dc) + self:renderOverlay() + + dc:clear():seek(1,1):pen(COLOR_WHITE):string("Digshape - Main menu") + dc:seek(1,3) + if true or self.state=="preview" then + for _, data in pairs(digButtons) do + builder = dc:key_string(data.keybind, data.text, self.activeDesgination==data.key and COLOR_WHITE or COLOR_GREY):newline(1) + if data.key=='x' then + builder:newline(1) + end + end + for _, data in pairs(buttons) do + builder = dc:key_string(data.keybind, data.text, COLOR_GREY):newline(1) + if data.key=='m' then + builder:newline(1) + end + end + + + --[[ dc:key_string("CUSTOM_S", "Set Brush",COLOR_GREY) + dc:newline():newline(1) + dc:key_string("CUSTOM_H", "Flip Horizontal",COLOR_GREY):newline(1) + dc:key_string("CUSTOM_V", "Flip Vertical",COLOR_GREY):newline(1) + dc:key_string("CUSTOM_R", "Rotate 90",COLOR_GREY):newline(1) + dc:key_string("CUSTOM_T", "Rotate -90",COLOR_GREY):newline(1) + dc:key_string("CUSTOM_G", "Cycle Corner",COLOR_GREY):newline(1) + dc:key_string("CUSTOM_I", "Invert",COLOR_GREY):newline(1) + dc:key_string("CUSTOM_C", "Convert to...",COLOR_GREY):newline(1) + dc:newline(1) + dc:key_string("CUSTOM_E", (self.option=="erase" and "Erasing" or "Erase"),self.option=="erase" and COLOR_RED or COLOR_GREY):newline(1) --make red + dc:key_string("CUSTOM_X", (self.option=="construction" and "Removing" or "Remove").." Constructions",self.option=="construction" and COLOR_GREEN or COLOR_GREY):newline(1) --make red + dc:newline():newline(1) + dc:key_string("CUSTOM_B", "Blink Brush",self.blink and COLOR_WHITE or COLOR_GREY):newline(1) + dc:newline() ]] + end + + dc:newline():newline(1):key_string("LEAVESCREEN", "Back") +end + + +function DigshapeUI:onInput(keys) + if df.global.cursor.x==-30000 then + local vp=self:getViewport() + df.global.cursor=xyz2pos(math.floor((vp.x1+math.abs((vp.x2-vp.x1))/2)+.5),math.floor((vp.y1+math.abs((vp.y2-vp.y1)/2))+.5), vp.z) + return + end + for k,v in pairs(keys) do + if k:match("^A_MOVE_") then + self.refresh = 1 + end + end + if true or self.state=="preview" then + for _, data in ipairs(digButtons) do + if keys[data.keybind] then + self.activeDesgination = data.key + self:runCurrentCommand(true) + end + end + for _, data in ipairs(buttons) do + if keys[data.keybind] then + data.callback(self) + end + end + if keys.SELECT then + --self:pasteBuffer(copyall(df.global.cursor)) + end + end + + if keys.LEAVESCREEN then + self:dismiss() + elseif self:propagateMoveKeys(keys) then + return + end +end + +if not (dfhack.gui.getCurFocus():match("^dwarfmode/Default") or dfhack.gui.getCurFocus():match("^dwarfmode/Designate") or dfhack.gui.getCurFocus():match("^dwarfmode/LookAround"))then + qerror("This screen requires the main dwarfmode view or the designation screen") +end + +local list = DigshapeUI{state="mark", blink=false,cull=true} +list:show() \ No newline at end of file From 59d9cfb613d68e677fbf1b553de21cdb4b4ac83d Mon Sep 17 00:00:00 2001 From: Quatch Date: Thu, 25 Mar 2021 18:10:06 -0400 Subject: [PATCH 02/12] Tidying and commenting Regrouped functions, added more comments. --- digshape.rb | 418 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 307 insertions(+), 111 deletions(-) diff --git a/digshape.rb b/digshape.rb index 295f826..7780c54 100644 --- a/digshape.rb +++ b/digshape.rb @@ -3,6 +3,7 @@ digshape ======= +digshape allows the creation of a number of repetative geometric designations. The script can be called manually, or through gui/digshape. Commands that do not require a set origin: @@ -11,22 +12,25 @@ To undo the previous command (restoring designation): digshape undo + To flood fill with a designation, overwriting ONLY the designation under the cursor: + digshape flood + Commands that require an origin to be set: To set the origin for drawing: digshape origin - To draw to the target point: + To draw to the cursor: digshape line To draw an ellipse using the origin and target as a bounding box: digshape ellipse (filled? [default: false]) - To draw an ellipse using the origin and target as a major axis, and the cursor as the length of the semiminor axis: - digshape major (to set the major axis endpoint) + To draw an ellipse using the origin and target as a major axis, and the cursor as the length of the semiminor axis (aka width [dist from cursor to line]): + digshape major (to set the major axis endpoint [the length]) digshape ellipse3p - To draw a circle using an arbitrary diameter: + To draw a circle using an arbitrary diameter (gives slightly different results to digcircle): digshape circle2p To draw a 3 pt bezier curve, with an arbitrary float for weighting the sharpness: @@ -35,32 +39,89 @@ To draw a polygon using the origin as the center and the cursor as the radius|apothem (radius) digshape polygon [radius|apothem] [digMode] - - To flood fill with a designation, overwriting ONLY the designation under the cursor: - digshape flood + + To draw a star using the origin as the center and the cursor as the radius|apothem (radius) + digshape star [skip] [digMode] To move all of the markers to the current z level: digshape resetz All commands accept a digging designation mode as a single character argument [dujihrx], otherwise will default to 'd' +=end + + + + + + +=begin +============ SCRIPT DESCRIPTION ============ +Digshape operates directly on the current mapstate. An undo buffer is maintained for the last-run command, and for the markers. + +This file is broken into segments. +=GUI interfacing + code required to allow headless operation through gui/digshape. + +=Utility functions + generic helper functions for script -TODO: mark origin should not change the digging designation, ellipse cleanup should restore not clear it. +=Data structures + structure to hold coordinates, and to interpret the designations. +=Cursor functions + code to interact with the map (df(hack)). Get, set, undo. + +=Shape functions + functions to draw geometries. + +=Script control + print help, functions to parse arguments, untap, upkeep. + +=Commands + user-callable interactions with digshape. Each command is responsible for getting arguments it needs, calling the functions to make it's geometry, and plotting them to the map (frequently a side-effect of the geometry function). + +============ KNOWN BUGS ============ + BUG: "digshape polygon 3 r" uses both 'radius' and digmode:'r' + BUG: "digshape polygon 2 apothem" does not work, but higher numbers do. + + + +============ TODO ============ + TODO: mark origin should not change the digging designation, ellipse cleanup should restore not clear it. + TODO: replace text dig designations with dfhack enums + TODO: convert digshape to lua script + TODO: just always use the current Z level. (ensure undo) + TODO: rename control points to A,B,C,... for more generality and faster discussion. Maybe origin+ABCD...? [Origin, Cursor, A,B,C,D,...] + TODO: add marker mode/toggle marker designation, smooth, engrave, carveFortification + + + + +============ FEATURE IDEAS ============ + IDEA: Pixel fonts (size) + IDEA: Gradient fill. (2pt box, 1pt midpoint, arg: direction[NSEW,diags,star,pit,rings,etc]) + IDEA: 3d shapes (eg platonic solids) + + IDEA: circle3p (given any 3p) (((Ali Sheikhpour (https://math.stackexchange.com/users/707123/ali-sheikhpour), Get the equation of a circle when given 3 points, URL (version: 2021-01-26): https://math.stackexchange.com/q/4000949))) + IDEA: arc 3p (as circle3p but only draw inside bbox) + IDEA: default digmode is whatever is selected currently in the active df:designationMode screen + IDEA: add diagonal adjacency to floodfill as an option =end -DigPos = Struct.new(:x, :y, :z) do - def to_s - return "(#{x},#{y},#{z})" - end - def clone - return DigPos.new(x,y,z) - end -end -def cursorAsDigPos() - return DigPos.new(df.cursor.x, df.cursor.y, df.cursor.z) -end -# really nasty hack so that lua can use digshape + + + + + + +=begin +======================== DIGSHAPE GUI interfacing +really nasty hack so that lua can use digshape. + +GUI allows for 'preview' which returns the points digshape would dig, but does not modify the map. Printing and errors are redirected. +=end + $isLuaMode = $script_args[0] == "lua" $isPreviewOnly = false @@ -79,32 +140,18 @@ def writeLuaPos(name, digPos) # pos:::: end end -def setOrigin(x, y, z) # sets the origin and marks if it iff we are in console. cleans up last mark too. - $origin = DigPos.new(x,y,z) - # the rest is just really complicated logic to mark the origin if the user is using the console version - # it also has to play well with lua - if $oldOrigin then - oldTile = df.map_tile_at($oldOrigin.x, $oldOrigin.y, $oldOrigin.z) - oldTile.dig($oldOriginDesignation) if oldTile.shape_basic == $oldOriginShape && oldTile.designation.dig == digMode2enum('d') - end - if not $isLuaMode then - $oldOrigin = $origin.clone() - newOriginTile = df.map_tile_at($origin.x, $origin.y, $origin.z) - $oldOriginDesignation = newOriginTile.designation.dig - $oldOriginShape = newOriginTile.shape_basic - digAt($origin.x, $origin.y, $origin.z, 'd', buffer: false) - else - $oldOrigin = nil # don't undo our origins if we are in lua mode - end -end -def setMajor(x, y, z) - $major = DigPos.new(x,y,z) -end + + +=begin +======================== UTILITY FUNCTIONS +=end + def stdout(msg) + # print a message to the console if $isLuaMode == false then puts msg else @@ -112,7 +159,8 @@ def stdout(msg) end end -def stderr(msg) # write an error mesage +def stderr(msg) + # print an error message to the console if $isLuaMode == false then puts " Error: "+msg else @@ -120,51 +168,66 @@ def stderr(msg) # write an error mesage end end -def scriptError(msg) # call this when you reach corner cases / the fault perhaps isn't the user +def scriptError(msg) + # call this when you reach corner cases / the fault perhaps isn't the user. Script terminates. stderr(msg) raise "oopsie! script errored! ;)" end -def userSucks(msg) # call this when we don't like the user's input +def userSucks(msg) + # call this when we don't like the user's input. Script may continue. stderr(msg) throw :script_finished end -def undo() # BUG! Does not keep track of Z levels - #Execute one level of undo. - #z level is presumed to be the current. - # todo, have multiple levels of undo / redo - i=$digBufferX.length - newBufferX = [] - newBufferY = [] - newBufferZ = [] # redundant, i.e. always the same most of the time, but needed so that we use it as a pointer for digAt. Also supports digging multi dimenisional shapes - newBufferD = [] - while i > 0 do - x=$digBufferX.pop - y=$digBufferY.pop - z=$digBufferZ.pop - d=$digBufferD.pop - digAt(x,y,z, enum2digMode(d), buffer: true, bufferX: newBufferX, bufferY: newBufferY, bufferZ: newBufferZ, bufferD: newBufferD) - i = i-1 + + + + + + +=begin +======================== DATA STRUCTURES +=end + + +DigPos = Struct.new(:x, :y, :z) do + #Holds a 3d coordinate + def to_s + return "(#{x},#{y},#{z})" + end + def clone + return DigPos.new(x,y,z) end - #clear buffer for next dig - $digBufferX = newBufferX - $digBufferY = newBufferY - $digBufferZ = newBufferZ - $digBufferD = newBufferD end +#Control points: + #$origin + #$major + #$cursor def clearDigBuffer() - #clear buffer for next dig, or initialize it's existance on first run. + #$digBuffer* is a set of global arrays containing the 3d coordinates and their prior dig designations. + + #clear buffer for next dig, or initialize if empty. $digBufferX=[] $digBufferY=[] $digBufferZ=[] $digBufferD=[] end + +def getDigMode(digMode = 'd') +#TODO just integrate this into getDigModeArgument. + if ['d', 'u', 'j', 'i', 'h', 'r', 'x'].include? digMode then + return digMode + end + return 'd' +end + + def digMode2enum(digMode) #this function turns a digmode into the appropriate enum for easier comparison on tile reading (eg floodfill.) case digMode #from https://github.com/DFHack/scripts/blob/master/digfort.rb @@ -196,6 +259,78 @@ def enum2digMode(digEnum) end end + + + +=begin +======================== CURSOR FUNCTIONS +=end + + +def cursorAsDigPos() + #returns current cursor position as a DigPos + return DigPos.new(df.cursor.x, df.cursor.y, df.cursor.z) +end + + + + +def setOrigin(x, y, z) + # sets the origin and marks if it iff we are in console. cleans up last mark too. + #TODO: make 'controlpoints' an array indexed by name, so that all can be operated on in the same way. + $origin = DigPos.new(x,y,z) + # the rest is just really complicated logic to mark the origin if the user is using the console version + # it also has to play well with lua + + if $oldOrigin then + oldTile = df.map_tile_at($oldOrigin.x, $oldOrigin.y, $oldOrigin.z) + oldTile.dig($oldOriginDesignation) if oldTile.shape_basic == $oldOriginShape && oldTile.designation.dig == digMode2enum('d') + end + if not $isLuaMode then + $oldOrigin = $origin.clone() + newOriginTile = df.map_tile_at($origin.x, $origin.y, $origin.z) + $oldOriginDesignation = newOriginTile.designation.dig + $oldOriginShape = newOriginTile.shape_basic + + digAt($origin.x, $origin.y, $origin.z, 'd', buffer: false) + else + $oldOrigin = nil # don't undo our origins if we are in lua mode + end +end + +def setMajor(x, y, z) + # Assigns the control point 'major' to the current cursor location + $major = DigPos.new(x,y,z) +end + + + +def undo() + #Execute one level of undo. + #z level is presumed to be the current. + #BUG: Does not keep track of Z levels + #TODO: have multiple levels of undo / redo + i=$digBufferX.length + newBufferX = [] + newBufferY = [] + newBufferZ = [] # redundant, i.e. always the same most of the time, but needed so that we use it as a pointer for digAt. Also supports digging multi dimenisional shapes + newBufferD = [] + while i > 0 do + x=$digBufferX.pop + y=$digBufferY.pop + z=$digBufferZ.pop + d=$digBufferD.pop + digAt(x,y,z, enum2digMode(d), buffer: true, bufferX: newBufferX, bufferY: newBufferY, bufferZ: newBufferZ, bufferD: newBufferD) + i = i-1 + end + #clear buffer for next dig + $digBufferX = newBufferX + $digBufferY = newBufferY + $digBufferZ = newBufferZ + $digBufferD = newBufferD +end + + def isDigPermitted(digMode, tileShape) # can we dig on this tile? @@ -211,6 +346,8 @@ def isDigPermitted(digMode, tileShape) end def digAt(x, y, z, digMode = 'd', buffer: true, bufferX: $digBufferX, bufferY: $digBufferY, bufferZ: $digBufferZ, bufferD: $digBufferD) + #Commit designation@coords to the map, opt save current value there to the buffer for undo. + tile = df.map_tile_at(x, y, z) # check if the tile returned is valid, ignore if its not (out of bounds, air, etc) @@ -238,8 +375,16 @@ def digAt(x, y, z, digMode = 'd', buffer: true, bufferX: $digBufferX, bufferY: $ -# https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm + + +=begin +======================== SHAPES FUNCTIONS +=end + + def drawLineLow(x0, y0, z0, x1, y1, z1, digMode = 'd') + # Helper function for drawLine. + # Uses: https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm dx = x1 - x0 dy = y1 - y0 yi = 1 @@ -264,6 +409,8 @@ def drawLineLow(x0, y0, z0, x1, y1, z1, digMode = 'd') end def drawLineHigh(x0, y0, z0, x1, y1, z1, digMode = 'd') + # Helper function for drawLine. + # Uses: https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm dx = x1 - x0 dy = y1 - y0 xi = 1 @@ -287,6 +434,8 @@ def drawLineHigh(x0, y0, z0, x1, y1, z1, digMode = 'd') end def drawLine(x0, y0, z0, x1, y1, z1, digMode = 'd') + # Draw a straight, line between two points. + # Uses: https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm if (y1 - y0).abs < (x1 - x0).abs then if x0 > x1 then drawLineLow(x1, y1, z1, x0, y0, z0, digMode) @@ -303,11 +452,14 @@ def drawLine(x0, y0, z0, x1, y1, z1, digMode = 'd') end def plotQuadRationalBezierSeg(x0, y0, z0, x1, y1, z1, x2, y2, z2, w, digMode = 'd') - #/* plot a limited rational Bezier segment, squared weight */ - #http://members.chello.at/easyfilter/bresenham.pdf listing 12 + # Helper function for plotQuadRationalBezier, draws one portion of the curve. + +=begin + /* plot a limited rational Bezier segment, squared weight */ + Source: http://members.chello.at/easyfilter/bresenham.pdf listing 12 #p0:origin, p1:weight, p2:termination #w is the weighting. "For w =1 the curve is a parabola, for w < 1 the curve is an ellipse, for w = 0 the curve is a straight line and for w>1 the curve is a hyperbola. The weights are normally assumed to be all positive." - +=end x0 = x0.floor #start with integer locations. Original code stores in int, so this is implicit. x1 = x1.floor x2 = x2.floor @@ -419,8 +571,9 @@ def plotQuadRationalBezierSeg(x0, y0, z0, x1, y1, z1, x2, y2, z2, w, digMode = ' end def plotQuadRationalBezier(x0, y0, z0, x1, y1, z1, x2, y2, z2, w=1.5, digMode = 'd') - #http://members.chello.at/easyfilter/bresenham.pdf listing 11 - ## plot any quadratic rational Bezier curve */ + #Draw a bezier between origin[p0] and control point[p2], pulled out towards cursor[p1] (by a weighting of 'w') + + # Source: http://members.chello.at/easyfilter/bresenham.pdf listing 11: /* plot any quadratic rational Bezier curve */ x = x0 - 2 * x1 + x2 y = y0 - 2 * y1 + y2 @@ -507,18 +660,20 @@ def plotQuadRationalBezier(x0, y0, z0, x1, y1, z1, x2, y2, z2, w=1.5, digMode end def plotRotatedEllipse(x, y, z, a, b, angle, digMode='d') - ## plot ellipse rotated by angle (radian) */ - #taken from: http://members.chello.at/easyfilter/bresenham.pdf listing 13. Explicitly released without copyright - #Note: most of this function deals with the ellipse at the origin. Translation to coordinates is at final call. + # Helper function for drawEllipse(). Draw an ellipse(center, major len, minor len) rotated by angle (radian) + +=begin + Source: http://members.chello.at/easyfilter/bresenham.pdf listing 13. Explicitly released without copyright + Note: most of this function deals with the ellipse at the origin. Translation to coordinates is at final call. - #x,y is the coodinates of the center - #a is __SEMI__major length - #b is __SEMI__minor length - #angle (radians), prob measured CCW from east + x,y is the coodinates of the center + a is __SEMI__major length + b is __SEMI__minor length + angle (radians), prob measured CCW from east - #A far more readable paper on plotting rotated ellipses (no pseudocode): http://www.crbond.com/papers/ell_alg.pdf - #Another paper on rasterizing 2d primitives: https://cs.brown.edu/research/pubs/theses/masters/1989/dasilva.pdf - + A far more readable paper on plotting rotated ellipses (no pseudocode): http://www.crbond.com/papers/ell_alg.pdf + Another paper on rasterizing 2d primitives: https://cs.brown.edu/research/pubs/theses/masters/1989/dasilva.pdf +=end angle = -angle #deal with -y axis. xd = a * a @@ -554,7 +709,7 @@ def plotRotatedEllipseRect(x0, y0, z0, x1, y1, zd, digMode='d') if (zd == 0) #Special case: no rotation. Use standard method. /* looks nicer */ #this should never be reached, as we call this from the regular ellipse function. - stdout "zd=0 degenerate case" + stdout("zd=0 degenerate case") drawEllipse(x0,y0,z0, x1,y1,z0) return end @@ -565,7 +720,7 @@ def plotRotatedEllipseRect(x0, y0, z0, x1, y1, zd, digMode='d') end if not(w <= 1.0 && w >= 0.0) then #/* limit angle to |zd|<=xd*yd */ - scriptError "Limit angle to |zd|<=xd*yd" + scriptError("Limit angle to |zd|<=xd*yd") end ## snap xe,ye to int */ @@ -580,6 +735,9 @@ def plotRotatedEllipseRect(x0, y0, z0, x1, y1, zd, digMode='d') end def drawEllipse(x0, y0, z0, x1, y1, z1, x2=nil, y2=nil, z2=nil, filled = false, digMode = 'd', mode = 'bbox') + #Draw an ellipse, using the current control points, in the method specified by mode. + +=begin # A Fast Bresenham Type Algorithm For Drawing Ellipses http://homepage.smc.edu/kennedy_john/belipse.pdf (https://www.dropbox.com/s/3q89g566u115g3q/belipse.pdf?dl=0) # also adapted from https://github.com/teichgraf/WriteableBitmapEx/blob/master/Source/WriteableBitmapEx/WriteableBitmapShapeExtensions.cs used under the MIT license @@ -587,6 +745,13 @@ def drawEllipse(x0, y0, z0, x1, y1, z1, x2=nil, y2=nil, z2=nil, filled = false, #p1 [xyz]: termination of major axis; OR the other corner of the bbox #p2 [xyz]: the extent (not a point nessisarily on the minor axis..) of the minor _radius_; aka, a point on the bounding box long side that will be used to determine the length of the short side. + #mode ['bbox']: + -'diameter': make a circle given 2p as the diameter + -'axis': make an ellipse along the line [origin, major], with the cursor setting the width. width is the distance from the cursor to the line. + -'bbox': generate an ellipse to fit entirely within the bounding box of [origin, cursor] + -IDEA: '5p': given 5p draw an ellipse that fits. +=end + xl = [x0, x1].min # find left edge xr = [x0, x1].max # find right edge yb = [y0, y1].min # find lower edge @@ -758,6 +923,7 @@ def drawEllipse(x0, y0, z0, x1, y1, z1, x2=nil, y2=nil, z2=nil, filled = false, end def digKeupoStair(x, y, z, depth) + #Dig an X of updown stairs (corners and center of a 3x3) centered on cursor, down a number of zlevels. iz = z digAt(x, y, iz, 'j') digAt(x - 1, y + 1, iz, 'j') @@ -774,15 +940,9 @@ def digKeupoStair(x, y, z, depth) end end -def getDigMode(digMode = 'd') - if ['d', 'u', 'j', 'i', 'h', 'r', 'x'].include? digMode then - return digMode - end - return 'd' -end - def drawPolygon(x0, y0, z0, x1, y1, z1, n = 3, apothem=false, digMode = 'd') - # if you dig a 2-gon (aka a line) it always passes through the origin so it's still convienient / useful + #Draw a polygon centered on origin with cursor at (apothem==T: midpoint of a side, ==F: vertex) + # if you dig a 2-gon (aka a line) it always passes through the origin so it's still convienient / useful. In apothem==T this makes the origin the midpoint of the drawn line. xOffset = x1 - x0 yOffset = y1 - y0 @@ -813,6 +973,7 @@ def drawPolygon(x0, y0, z0, x1, y1, z1, n = 3, apothem=false, digMode = 'd') end def drawStar(x0, y0, z0, x1, y1, z1, n = 5, skip = 2, digMode = 'd') + #Draw a star centered at origin, with cursor at a vertex. xOffset = x1 - x0 yOffset = y1 - y0 @@ -836,9 +997,9 @@ def drawStar(x0, y0, z0, x1, y1, z1, n = 5, skip = 2, digMode = 'd') end end - - def floodfill(x,y,z,targetDig, digMode, maxCounter= 10000) + #Flood fills out from the cursor until different designation reached (eg if on 'd' fill only 'd', if on empty, fill only empty). Rooks move adjacency only. + #targetDig: what designation type can we overwrite? #digMode: what designation are we placing? #maxCounter: a limit to help with performance. @@ -848,7 +1009,7 @@ def floodfill(x,y,z,targetDig, digMode, maxCounter= 10000) if not t then #ignore impossible tiles (eg air.) - stdout "Tile does not exist" + stdout("Tile does not exist") throw :script_finished return end @@ -857,7 +1018,7 @@ def floodfill(x,y,z,targetDig, digMode, maxCounter= 10000) if t.designation.dig == digNum then #don't dig tiles that are already dug - stdout "Tile is already dug" + stdout("Tile is already dug") throw :script_finished return end @@ -898,8 +1059,8 @@ def floodfill(x,y,z,targetDig, digMode, maxCounter= 10000) counter = counter -1 if counter <=0 then - stdout " Max coverage of #{maxCounter} tiles reached. Use multiple floods, or add a number for max coverage as 'digshape flood [max coverage] [dig type]'." - stdout " Automatically cancelling flood" + stdout(" Max coverage of #{maxCounter} tiles reached. Use multiple floods, or add a number for max coverage as 'digshape flood [max coverage] [dig type]'.") + stdout(" Automatically cancelling flood") undo() return end @@ -923,10 +1084,21 @@ def floodfill(x,y,z,targetDig, digMode, maxCounter= 10000) end end -# script execution start + + + + + + + + +=begin +======================== SCRIPT CONTROL +script execution start +=end if not $script_args[0] or $script_args[0]=="help" or $script_args[0]=="?" then - stdout " To draw downstair: digshape downstair depth" + stdout " To set origin: digshape origin" stdout " To draw line after origin is set: digshape line" stdout " To draw ellipse after origin is set (as bounding box): digshape ellipse " @@ -936,6 +1108,8 @@ def floodfill(x,y,z,targetDig, digMode, maxCounter= 10000) stdout " To draw a polygon after origin is set (as center) with the cursor as a vertex: digshape polygon <# sides>" stdout " To draw a polygon after origin is set (as center) with the cursor as a midpoint of a segment(apothem): digshape polygon <# sides> apothem" stdout " To draw a star after origin is set (as center) with the cursor as a vertex : digshape star <# points> " + stdout " To draw downstair: digshape downstair depth" + stdout " " stdout " To flood fill with a designation, overwriting ONLY the designation under the cursor (warning: slow on areas bigger than 10k tiles..): digshape flood [maxArea=10000]" stdout " To undo the previous command (restoring designation): digshape undo" stdout " To move all markers to the current z level (without displaying them): digshape resetz" @@ -947,7 +1121,7 @@ def floodfill(x,y,z,targetDig, digMode, maxCounter= 10000) $script_args.delete_at(0) if df.cursor.x == -30000 then - userSucks "Cursor must be on map" + userSucks("Cursor must be on map") end if not (command == 'undo' or command=='u') and not $isPreviewOnly then @@ -955,6 +1129,7 @@ def floodfill(x,y,z,targetDig, digMode, maxCounter= 10000) end def requireOriginZLevel(msg: "Origin and target must be on the same z-level (use command 'digshape resetz' or 'digshape setz [Z-level, default=Cursor Z]' to fix)") + #Ensure cursor is on same z as origin (TODO: and control points). if df.cursor.z != $origin.z then userSucks(msg) end @@ -962,6 +1137,7 @@ def requireOriginZLevel(msg: "Origin and target must be on the same z-level (use end def requireMajor(msg: "Set a point for the end of the major axis with the cursor and 'digshape major'") + #Ensure control point: 'major' has been set and is valid. if $major == nil then userSucks(msg) end @@ -971,14 +1147,20 @@ def requireMajor(msg: "Set a point for the end of the major axis with the cursor end def getDigModeArgument(args) + #get next[LAST] script argument IFF it is a digmode designation, or set default if not present. argument = args[0] digMode = getDigMode(argument) + #if not ['d', 'u', 'j', 'i', 'h', 'r', 'x'].include? digMode then + # digMode='d' + #end args.delete_at(0) return digMode end -def getFilledArgument(args, default: false) # this doesn't *expect* and argument and so only consumes an argument when something matches +def getFilledArgument(args, default: false) + #get next script argument IFF it is fill. + # this doesn't *expect* and argument and so only consumes an argument when something matches argument = args[0] case argument when 'filled', 'f', 'true', 'yes', 'y'; filled = true @@ -991,6 +1173,7 @@ def getFilledArgument(args, default: false) # this doesn't *expect* and argument end def getFloatArgument(args, default: nil, type: "(unnamed number)", positive: true) + #get next script argument, which must be a float. num = args[0] result = nil defaultMessage = "" @@ -1020,6 +1203,7 @@ def getFloatArgument(args, default: nil, type: "(unnamed number)", positive: tru end def getIntegerArgument(args, default: nil, type: "(unnamed integer)", positive: true) + #get next script argument, which must be an integer. num = args[0] result = nil defaultMessage = "" @@ -1082,10 +1266,20 @@ def noMoreArguments(args) end end + + + + +=begin +======================== DIGSHAPE COMMANDS +=end + + #def registerCommand(name, aliases, usage) # return "TODO:" #end + case command when 'origin', 'o', 'set' # $usage = createUsage(name: 'origin', aliases: ['o', 'set']) # TODO ADD USAGES TO EACH COMMANDS, make scriptError/userSucks print them out @@ -1108,9 +1302,8 @@ def noMoreArguments(args) writeLuaPos("major", $major) - + stdout("Now move the cursor to the minor axis radius (extent) and call ellipse3p") - stdout "Now move the cursor to the minor axis radius (extent) and call ellipse3p" when 'resetz', 'setz' z = df.cursor.z # default @@ -1123,10 +1316,11 @@ def noMoreArguments(args) if $major then setMajor($major.x, $major.y, z) end + when 'ls', 'status' - stdout "origin: #{$origin != nil ? $origin.to_s : ''}" - stdout "major : #{$major != nil ? $major.to_s : ''}" - stdout "cursor: #{cursorAsDigPos().to_s}" + stdout("origin: #{$origin != nil ? $origin.to_s : ''}") + stdout("major : #{$major != nil ? $major.to_s : ''}") + stdout("cursor: #{cursorAsDigPos().to_s}") when 'line', 'l' digMode = getDigModeArgument($script_args) @@ -1163,7 +1357,7 @@ def noMoreArguments(args) requireMajor() if filled then - stdout "Filled not yet supported for 3p ellipses." + stdout("Filled not yet supported for 3p ellipses.") filled = false end @@ -1215,10 +1409,11 @@ def noMoreArguments(args) noMoreArguments($script_args) if depth <= 0 then - userSucks "Depth must be an integer greater than zero" + userSucks("Depth must be an integer greater than zero") end digKeupoStair(df.cursor.x, df.cursor.y, df.cursor.z, depth) + when 'flood', 'f' maxArea = getIntegerArgument($script_args, default: 10000, type: "maximum flood area") digMode = getDigModeArgument($script_args) @@ -1230,14 +1425,15 @@ def noMoreArguments(args) if targetDig != :No then if targetDig == digMode2enum(digMode) then - userSucks "Floodfill must be centered on an undesignated/matching tile." + userSucks("Floodfill must be centered on an undesignated/matching tile.") end end floodfill(df.cursor.x, df.cursor.y, df.cursor.z, targetDig, digMode, maxArea) + when 'undo', 'u' undo() else - userSucks "Invalid command" + userSucks("Invalid command") end \ No newline at end of file From 3907dea586f640499dacb747ac025d9f0d85f828 Mon Sep 17 00:00:00 2001 From: Quatch Date: Thu, 25 Mar 2021 15:17:25 -0400 Subject: [PATCH 03/12] digshape refactor and gui, from flavourstreet Added flavourstreet's refactor of digshape, and it's new lua gui [keupo's discord, 2021-02-17] [8:10 PM]flavorstreet: Attachment file type: archive digshape-gui-ALPHA.zip 14.32 KB [8:12 PM]flavorstreet: Yall can do whatever u want with it i'll probably never get around to fixing the bugs im so burned out lol --811767111344193566 --- digshape.rb | 806 +++++++++++++++++++++++++++-------------------- gui/digshape.lua | 237 ++++++++++++++ 2 files changed, 695 insertions(+), 348 deletions(-) create mode 100644 gui/digshape.lua diff --git a/digshape.rb b/digshape.rb index 919ba50..7fc8013 100644 --- a/digshape.rb +++ b/digshape.rb @@ -51,33 +51,113 @@ TODO: mark origin should not change the digging designation, ellipse cleanup should restore not clear it. =end +DigPos = Struct.new(:x, :y, :z) do + def to_s + return "(#{x},#{y},#{z})" + end + def clone + return DigPos.new(x,y,z) + end +end + +def cursorAsDigPos() + return DigPos.new(df.cursor.x, df.cursor.y, df.cursor.z) +end + +# really nasty hack so that lua can use digshape +$isLuaMode = $script_args[0] == "lua" +$isPreviewOnly = false + +if $isLuaMode then + $script_args.delete_at(0) + $isPreviewOnly = $script_args[0] == "preview" + if $isPreviewOnly then + $script_args.delete_at(0) + end + $output +end + +def writeLuaPos(name, digPos) # pos:::: + if $isLuaMode then + puts "pos:#{name}:#{digPos.to_s}" + end +end + +def setOrigin(x, y, z) # sets the origin and marks if it iff we are in console. cleans up last mark too. + $origin = DigPos.new(x,y,z) + # the rest is just really complicated logic to mark the origin if the user is using the console version + # it also has to play well with lua + + if $oldOrigin then + oldTile = df.map_tile_at($oldOrigin.x, $oldOrigin.y, $oldOrigin.z) + oldTile.dig($oldOriginDesignation) if oldTile.shape_basic == $oldOriginShape && oldTile.designation.dig == digMode2enum('d') + end + if not $isLuaMode then + $oldOrigin = $origin.clone() + newOriginTile = df.map_tile_at($origin.x, $origin.y, $origin.z) + $oldOriginDesignation = newOriginTile.designation.dig + $oldOriginShape = newOriginTile.shape_basic + + digAt($origin.x, $origin.y, $origin.z, 'd', buffer: false) + else + $oldOrigin = nil # don't undo our origins if we are in lua mode + end +end + +def setMajor(x, y, z) + $major = DigPos.new(x,y,z) +end + +def stdout(msg) + if $isLuaMode == false then + puts msg + else + puts "msg:"+msg + end +end -def markOrigin(ox, oy, oz) - t = df.map_tile_at(ox, oy, oz) - if t then - s = t.shape_basic - #TODO: preseve designation: - #$originTile = t.designation # a global to store the original origin state - #puts "origin: #{$originTile}" - t.dig(:Default) if s == :Wall +def stderr(msg) # write an error mesage + if $isLuaMode == false then + puts " Error: "+msg + else + puts "err:"+msg end end +def scriptError(msg) # call this when you reach corner cases / the fault perhaps isn't the user + stderr(msg) + raise "oopsie! script errored! ;)" +end + +def userSucks(msg) # call this when we don't like the user's input + stderr(msg) + throw :script_finished +end + + -def unDig() - #Exicute one level of undo. +def undo() # BUG! Does not keep track of Z levels + #Execute one level of undo. #z level is presumed to be the current. + # todo, have multiple levels of undo / redo i=$digBufferX.length - while i >= 0 do + newBufferX = [] + newBufferY = [] + newBufferZ = [] # redundant, i.e. always the same most of the time, but needed so that we use it as a pointer for digAt. Also supports digging multi dimenisional shapes + newBufferD = [] + while i > 0 do x=$digBufferX.pop y=$digBufferY.pop + z=$digBufferZ.pop d=$digBufferD.pop - - digAt(x,y,df.cursor.z, enum2dig(d), buffer=false) + digAt(x,y,z, enum2digMode(d), buffer: true, bufferX: newBufferX, bufferY: newBufferY, bufferZ: newBufferZ, bufferD: newBufferD) i = i-1 - end - #clear buffer for next dig. - clearDigBuffer() + end + #clear buffer for next dig + $digBufferX = newBufferX + $digBufferY = newBufferY + $digBufferZ = newBufferZ + $digBufferD = newBufferD end @@ -85,40 +165,83 @@ def clearDigBuffer() #clear buffer for next dig, or initialize it's existance on first run. $digBufferX=[] $digBufferY=[] + $digBufferZ=[] $digBufferD=[] end +def digMode2enum(digMode) + #this function turns a digmode into the appropriate enum for easier comparison on tile reading (eg floodfill.) + case digMode #from https://github.com/DFHack/scripts/blob/master/digfort.rb + when 'd'; return :Default + when 'u'; return :UpStair + when 'j'; return :DownStair + when 'i'; return :UpDownStair + when 'h'; return :Channel + when 'r'; return :Ramp + when 'x'; return :No + else + scriptError("Unknown digMode, `"+digMode+"', digMode must be any of 'd', 'u', 'j', 'i', 'h', 'r', or 'x', which correspond to the designation keys") + end +end -def digAt(x, y, z, digMode = 'd', buffer=true) - t = df.map_tile_at(x, y, z) - - #store the current tile's designation in a undo buffer - if buffer then - $digBufferX.push(x) - $digBufferY.push(y) - $digBufferD.push(t.designation.dig) + +def enum2digMode(digEnum) + #this function turns a designation enum into the appropriate digtype character + case digEnum #from https://github.com/DFHack/scripts/blob/master/digfort.rb + when :Default; return 'd' + when :UpStair; return 'u' + when :DownStair; return 'j' + when :UpDownStair; return 'i' + when :Channel; return 'h' + when :Ramp; return 'r' + when :No; return 'x' + else + scriptError("Unknown digEnum `#{digEnum.to_s}'") end +end + +def isDigPermitted(digMode, tileShape) + # can we dig on this tile? + + if not tileShape then return false end + case digMode + when 'd', 'u', 'i', 'r'; return tileShape == :Wall + when 'j', 'h'; return tileShape == :Wall || tileShape == :Floor + when 'x'; return true + else + scriptError("Unknown digMode: `"+digMode+"'") + end +end + +def digAt(x, y, z, digMode = 'd', buffer: true, bufferX: $digBufferX, bufferY: $digBufferY, bufferZ: $digBufferZ, bufferD: $digBufferD) + tile = df.map_tile_at(x, y, z) # check if the tile returned is valid, ignore if its not (out of bounds, air, etc) - if t then - s = t.shape_basic - - case digMode #from https://github.com/DFHack/scripts/blob/master/digfort.rb - when 'd'; t.dig(:Default) if s == :Wall - when 'u'; t.dig(:UpStair) if s == :Wall - when 'j'; t.dig(:DownStair) if s == :Wall or s == :Floor - when 'i'; t.dig(:UpDownStair) if s == :Wall - when 'h'; t.dig(:Channel) if s == :Wall or s == :Floor - when 'r'; t.dig(:Ramp) if s == :Wall - when 'x'; t.dig(:No) + if tile then + tileShape = tile.shape_basic + + if isDigPermitted(digMode, tileShape) then + if $isPreviewOnly then + puts "dig:"+digMode+":"+x.to_s+":"+y.to_s+":"+z.to_s else - puts " Error: Unknown digtype" - throw :script_finished + if buffer then # store the current tile's designation in a undo buffer + bufferX.push(x) + bufferY.push(y) + bufferZ.push(z) + bufferD.push(tile.designation.dig) + + end + tile.dig(digMode2enum(digMode)) + end end end end + + + + # https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm def drawLineLow(x0, y0, z0, x1, y1, z1, digMode = 'd') dx = x1 - x0 @@ -183,7 +306,6 @@ def drawLine(x0, y0, z0, x1, y1, z1, digMode = 'd') end end - def plotQuadRationalBezierSeg(x0, y0, z0, x1, y1, z1, x2, y2, z2, w, digMode = 'd') #/* plot a limited rational Bezier segment, squared weight */ #http://members.chello.at/easyfilter/bresenham.pdf listing 12 @@ -300,8 +422,6 @@ def plotQuadRationalBezierSeg(x0, y0, z0, x1, y1, z1, x2, y2, z2, w, digMode = ' drawLine(x0,y0,z0, x2,y2,z0, digMode) end - - def plotQuadRationalBezier(x0, y0, z0, x1, y1, z1, x2, y2, z2, w=1.5, digMode = 'd') #http://members.chello.at/easyfilter/bresenham.pdf listing 11 ## plot any quadratic rational Bezier curve */ @@ -390,7 +510,6 @@ def plotQuadRationalBezier(x0, y0, z0, x1, y1, z1, x2, y2, z2, w=1.5, digMode plotQuadRationalBezierSeg(x0, y0, z0, x1, y1, z0, x2, y2, z0, w * w, digMode) end - def plotRotatedEllipse(x, y, z, a, b, angle, digMode='d') ## plot ellipse rotated by angle (radian) */ #taken from: http://members.chello.at/easyfilter/bresenham.pdf listing 13. Explicitly released without copyright @@ -425,8 +544,6 @@ def plotRotatedEllipse(x, y, z, a, b, angle, digMode='d') plotRotatedEllipseRect(x - a, y - b, z, x + a, y + b, (4 * zd * Math.cos(angle)), digMode) end - - def plotRotatedEllipseRect(x0, y0, z0, x1, y1, zd, digMode='d') #http://members.chello.at/easyfilter/bresenham.pdf listing 13 #/* rectangle enclosing the ellipse, integer rotation angle */ @@ -441,7 +558,7 @@ def plotRotatedEllipseRect(x0, y0, z0, x1, y1, zd, digMode='d') if (zd == 0) #Special case: no rotation. Use standard method. /* looks nicer */ #this should never be reached, as we call this from the regular ellipse function. - puts "zd=0 degenerate case" + stdout "zd=0 degenerate case" drawEllipse(x0,y0,z0, x1,y1,z0) return end @@ -449,12 +566,11 @@ def plotRotatedEllipseRect(x0, y0, z0, x1, y1, zd, digMode='d') ## squared weight of P1 */ if (w != 0.0) then w = (w - zd) / (w + w) - end + end if not(w <= 1.0 && w >= 0.0) then #/* limit angle to |zd|<=xd*yd */ - puts " Error: Limit angle to |zd|<=xd*yd" - throw :script_finished - end + scriptError "Limit angle to |zd|<=xd*yd" + end ## snap xe,ye to int */ xd = (xd * w + 0.5).floor @@ -467,7 +583,6 @@ def plotRotatedEllipseRect(x0, y0, z0, x1, y1, zd, digMode='d') plotQuadRationalBezierSeg(x1, y1-yd, z0, x1,y0, z0, x0+xd,y0, z0, w, digMode) end - def drawEllipse(x0, y0, z0, x1, y1, z1, x2=nil, y2=nil, z2=nil, filled = false, digMode = 'd', mode = 'bbox') # A Fast Bresenham Type Algorithm For Drawing Ellipses http://homepage.smc.edu/kennedy_john/belipse.pdf (https://www.dropbox.com/s/3q89g566u115g3q/belipse.pdf?dl=0) # also adapted from https://github.com/teichgraf/WriteableBitmapEx/blob/master/Source/WriteableBitmapEx/WriteableBitmapShapeExtensions.cs used under the MIT license @@ -725,57 +840,7 @@ def drawStar(x0, y0, z0, x1, y1, z1, n = 5, skip = 2, digMode = 'd') end end -def dig2enum(digMode) - #this function turns a digmode into the appropriate enum for easier comparison on tile reading (eg floodfill.) - case digMode #from https://github.com/DFHack/scripts/blob/master/digfort.rb - when 'd'; return :Default - when 'u'; return :UpStair - when 'j'; return :DownStair - when 'i'; return :UpDownStair - when 'h'; return :Channel - when 'r'; return :Ramp - when 'x'; return :No - else - puts " Error: Unknown digtype" - throw :script_finished - end -end - -def enum2dig(digEnum) - #this function turns a designation enum into the appropriate digtype character - case digEnum #from https://github.com/DFHack/scripts/blob/master/digfort.rb - when :Default; return 'd' - when :UpStair; return 'u' - when :DownStair; return 'j' - when :UpDownStair; return 'i' - when :Channel; return 'h' - when :Ramp; return 'r' - when :No; return 'x' - else - puts " Error: Unknown digEnum" - throw :script_finished - end -end - -def digPermitted(digMode, tileShape_basic) - #can we dig on this tile? - - if not tileShape_basic then return false end - - case digMode - when 'd'; if tileShape_basic == :Wall then return true else return false end - when 'u'; if tileShape_basic == :Wall then return true else return false end - when 'j'; if tileShape_basic == :Wall || tileShape_basic == :Floor then return true else return false end - when 'i'; if tileShape_basic == :Wall then return true else return false end - when 'h'; if tileShape_basic == :Wall || tileShape_basic == :Floor then return true else return false end - when 'r'; if tileShape_basic == :Wall then return true else return false end - when 'x'; return true - else - puts " Error: Unknown digtype" - throw :script_finished - end - end def floodfill(x,y,z,targetDig, digMode, maxCounter= 10000) #targetDig: what designation type can we overwrite? @@ -787,14 +852,16 @@ def floodfill(x,y,z,targetDig, digMode, maxCounter= 10000) if not t then #ignore impossible tiles (eg air.) + stdout "Tile does not exist" throw :script_finished return end - digNum = dig2enum(digMode) #Stash this, we'll use it many times. + digNum = digMode2enum(digMode) #Stash this, we'll use it many times. if t.designation.dig == digNum then #don't dig tiles that are already dug + stdout "Tile is already dug" throw :script_finished return end @@ -812,7 +879,7 @@ def floodfill(x,y,z,targetDig, digMode, maxCounter= 10000) xi = xw - 1 #move xw cursor west until it hits a match t=df.map_tile_at(xi,y,z) - if !t || xi == 0 || t.designation.dig != targetDig || !digPermitted(digMode,t.shape_basic) then + if !t || xi == 0 || t.designation.dig != targetDig || !isDigPermitted(digMode,t.shape_basic) then break end xw = xi @@ -823,7 +890,7 @@ def floodfill(x,y,z,targetDig, digMode, maxCounter= 10000) xi = xe + 1 #move xe cursor east until it hits a match t=df.map_tile_at(xi,y,z) - if !t || xi == 0 || t.designation.dig != targetDig || !digPermitted(digMode,t.shape_basic) then + if !t || xi == 0 || t.designation.dig != targetDig || !isDigPermitted(digMode,t.shape_basic) then break end xe = xi @@ -835,18 +902,20 @@ def floodfill(x,y,z,targetDig, digMode, maxCounter= 10000) counter = counter -1 if counter <=0 then - puts " Max coverage of #{maxCounter} tiles reached. Use multiple floods, or add a number for max coverage as 'digshape flood [max coverage] [dig type]'." + stdout " Max coverage of #{maxCounter} tiles reached. Use multiple floods, or add a number for max coverage as 'digshape flood [max coverage] [dig type]'." + stdout " Automatically cancelling flood" + undo() return end #check N/S t = df.map_tile_at(xi,y+1,z) - if t && t.designation.dig == targetDig && digPermitted(digMode,t.shape_basic) then + if t && t.designation.dig == targetDig && isDigPermitted(digMode,t.shape_basic) then xStack.push(xi) yStack.push(y+1) end t = df.map_tile_at(xi,y-1,z) - if t && t.designation.dig == targetDig && digPermitted(digMode,t.shape_basic) then + if t && t.designation.dig == targetDig && isDigPermitted(digMode,t.shape_basic) then xStack.push(xi) yStack.push(y-1) end @@ -907,283 +976,324 @@ def drawSpiral(x0, y0, z0, x1, y1, z1, coils, chord = 10, digMode = 'd') # script execution start if not $script_args[0] or $script_args[0]=="help" or $script_args[0]=="?" then - puts " To draw downstair: digshape downstair depth" - puts " To set origin: digshape origin" - puts " To draw line after origin is set: digshape line" - puts " To draw ellipse after origin is set (as bounding box): digshape ellipse " - puts "..To draw an ellipse after origin is set (by major and minor axis): digshape major (must be horizontal or vertical), then digshape ellipse3p" - puts " To draw a 3 point bezier curve after origin and major are set: digshape bez " - puts "..To draw a circle after origin is set, select any point as a diameter: digshape circle2p " - puts " To draw a polygon after origin is set (as center) with the cursor as a vertex: digshape polygon <# sides>" - puts " To draw a polygon after origin is set (as center) with the cursor as a midpoint of a segment(apothem): digshape polygon <# sides> apothem" - puts " To draw a star after origin is set (as center) with the cursor as a vertex : digshape star <# points> " - puts "To draw an Archimedean spiral (coils - number of coils, chord - distance between points): + stdout " To draw downstair: digshape downstair depth" + stdout " To set origin: digshape origin" + stdout " To draw line after origin is set: digshape line" + stdout " To draw ellipse after origin is set (as bounding box): digshape ellipse " + stdout "..To draw an ellipse after origin is set (by major and minor axis): digshape major (must be horizontal or vertical), then digshape ellipse3p" + stdout " To draw a 3 point bezier curve after origin and major are set: digshape bez " + stdout "..To draw a circle after origin is set, select any point as a diameter: digshape circle2p " + stdout " To draw a polygon after origin is set (as center) with the cursor as a vertex: digshape polygon <# sides>" + stdout " To draw a polygon after origin is set (as center) with the cursor as a midpoint of a segment(apothem): digshape polygon <# sides> apothem" + stdout " To draw a star after origin is set (as center) with the cursor as a vertex : digshape star <# points> " + stdout "To draw an Archimedean spiral (coils - number of coils, chord - distance between points): digshape spiral " - puts " To flood fill with a designation, overwriting ONLY the designation under the cursor (warning: slow on areas bigger than 10k tiles..): digshape flood [maxArea=10000]" - puts " To undo the previous command (restoring designation): digshape undo" - puts " To move all markers to the current z level (without displaying them): digshape resetz" - puts " All commands accept a one letter digging designation [dujihrx] at the end, or will default to 'd'" + stdout " To flood fill with a designation, overwriting ONLY the designation under the cursor (warning: slow on areas bigger than 10k tiles..): digshape flood [maxArea=10000]" + stdout " To undo the previous command (restoring designation): digshape undo" + stdout " To move all markers to the current z level (without displaying them): digshape resetz" + stdout " All commands accept a one letter digging designation [dujihrx] at the end, or will default to 'd'" throw :script_finished end command = $script_args[0] -argument1 = $script_args[1] -argument2 = $script_args[2] -argument3 = $script_args[3] +$script_args.delete_at(0) if df.cursor.x == -30000 then - puts " Error: cursor must be on map" - throw :script_finished + userSucks "Cursor must be on map" end -if command=="o" or command=="set" then #alias - command="origin" -elsif command=="keupo" or command=="stairs" or command=="downstairs" then - command="downstair" +if not (command == 'undo' or command=='u') and not $isPreviewOnly then + clearDigBuffer() #clear the dig buffer so we can undo the following command. Or initialize it's first run end -if command != 'undo' then - clearDigBuffer() #clear the dig buffer so we can undo the following command. Or initialize it's first run +def requireOriginZLevel(msg: "Origin and target must be on the same z-level (use command 'digshape resetz' or 'digshape setz [Z-level, default=Cursor Z]' to fix)") + if df.cursor.z != $origin.z then + userSucks(msg) + end + writeLuaPos("origin",$origin) # visualize them for the user end -case command - when 'origin' - $originx = df.cursor.x - $originy = df.cursor.y - $originz = df.cursor.z - - markOrigin($originx, $originy, $originz) - when 'resetz' - $originz = $majorz = df.cursor.z - when 'line' - dig = getDigMode(argument1) - - if df.cursor.z == $originz then - drawLine($originx, $originy, $originz, df.cursor.x, df.cursor.y, df.cursor.z, dig) +def requireMajor(msg: "Set a point for the end of the major axis with the cursor and 'digshape major'") + if $major == nil then + userSucks(msg) + end + requireOriginZLevel() + writeLuaPos("origin", $origin) # visualize them for the user + writeLuaPos("major", $major) # visualize them for the user +end + +def getDigModeArgument(args) + argument = args[0] + digMode = getDigMode(argument) + args.delete_at(0) + + return digMode +end + +def getFilledArgument(args, default: false) # this doesn't *expect* and argument and so only consumes an argument when something matches + argument = args[0] + case argument + when 'filled', 'f', 'true', 'yes', 'y'; filled = true + when 'hollow', 'h', 'false', 'no', 'n'; filled = false else - puts " Error: origin and target must be on the same z level" - throw :script_finished - end - when 'ellipse' #digshape ellipse [filled] [digmode] - filled = false - case argument1 - when 'filled'; filled = true - when 'hollow'; filled = false - when 'true'; filled = true - when 'false'; filled = false - when 't'; filled = true - when 'f'; filled = false - when 'y'; filled = true - when 'n'; filled = false - end + return default # doesn't consume if nothing matches + end + args.delete_at(0); + return filled +end - dig = getDigMode(argument1) #check argument 1 for dig instructions - if argument2 then # if argument 2 is present, look at that for dig instructions - dig = getDigMode(argument2) - end +def getFloatArgument(args, default: nil, type: "(unnamed number)", positive: true) + num = args[0] + result = nil + defaultMessage = "" - if df.cursor.z == $originz then - drawEllipse($originx, $originy, $originz, df.cursor.x, df.cursor.y, df.cursor.z, x2=nil, y2=nil, z2=nil, filled=filled, digMode=dig, mode = 'bbox') + if default != nil then + defaultMessage = "Use `-' for the default value (#{default})" + end - # remove origin designation - digAt($originx, $originy, $originz, 'x') + if not num then + userSucks("Must supply #{type} parameter (number).#{defaultMessage}") + end + args.delete_at(0) + + case num + when 'default','-'; + userSucks("No default value for #{type} parameter!") if default == nil + result = default else - puts " Error: origin and target must be on the same z level" - throw :script_finished - end - when 'circle2p' #digshape circle2p [filled] [digmode] - filled = false - case argument1 - when 'filled'; filled = true - when 'hollow'; filled = false - when 'true'; filled = true - when 'false'; filled = false - when 't'; filled = true - when 'f'; filled = false - when 'y'; filled = true - when 'n'; filled = false - end - - dig = getDigMode(argument1) #check argument 1 for dig instructions - if argument2 then # if argument 2 is present, look at that for dig instructions - dig = getDigMode(argument2) - end + result = Float(num) rescue userSucks("Malformed number for "+type+" parameter, got `"+num+"'.#{defaultMessage}") + end + + if positive && result<0 then + userSucks("Expected positive number for #{type} parameter, got `#{num}'.#{defaultMessage}") + end + + return result +end + +def getIntegerArgument(args, default: nil, type: "(unnamed integer)", positive: true) + num = args[0] + result = nil + defaultMessage = "" + + if default != nil then + defaultMessage = "Use `-' for the default value (#{default})" + end - if df.cursor.z == $originz then - drawEllipse($originx, $originy, $originz, df.cursor.x, df.cursor.y, df.cursor.z, x2=nil, y2=nil, z2=nil, filled=filled, digMode = dig, mode = 'diameter') + if not num then + userSucks("Must supply #{type} parameter (integer).#{defaultMessage}") + end + args.delete_at(0) + + case num + when 'default','-'; + userSucks("No default value for #{type} parameter!") if default == nil + result = default else - puts " Error: origin and target must be on the same z level" - throw :script_finished + result = Integer(num) rescue userSucks("Malformed integer for "+type+" parameter, got `"+num+"'.#{defaultMessage}") + end + + if positive && result<0 then + userSucks("Expected positive integer for #{type} parameter, got `#{num}'.#{defaultMessage}") + end + + return result +end + +def makeDefaultPosMap(oldPos) + return { + '~' => cursorAsDigPos(), # cursor positon + '-' => oldPos.clone() # old value of this (i.e. leave it unchanged) + } +end + +def getPosComponentArgument(args, defaultMap, symbol) + if defaultMap[args[0]] then # it is either ~ or - + value = defaultMap[args[0]][symbol] + args.delete_at(0) + return value + else # it is an integer + return getIntegerArgument(args, type: "#{symbol.to_s} coordinate", positive: true) + end +end + +def getPositionArgument(args, oldPos, default: nil) # X Y Z, ~ ~ ~, - - -, or any mix # returns nil if no default! + if args[0] && args[1] && args[2] then + defaultMap = makeDefaultPosMap(oldPos) + return DigPos.new(getPosComponentArgument(args, defaultMap, :x), + getPosComponentArgument(args, defaultMap, :y), + getPosComponentArgument(args, defaultMap, :z)) + else + return default + end +end + +def noMoreArguments(args) + if args[0] then + userSucks("Did not expect more arguments #{args}") + end +end + +#def registerCommand(name, aliases, usage) +# return "TODO:" +#end + +case command + when 'origin', 'o', 'set' + # $usage = createUsage(name: 'origin', aliases: ['o', 'set']) # TODO ADD USAGES TO EACH COMMANDS, make scriptError/userSucks print them out + # Even better, to refator all these commands with associated data into classes / anonoymous functions so we can eventually do digshape help + newOrigin = getPositionArgument($script_args, $origin, default: cursorAsDigPos()) + + noMoreArguments($script_args) + + setOrigin(newOrigin.x, newOrigin.y, newOrigin.z) # need to refactor setOrigin + + writeLuaPos("origin", $origin) + + when 'major', 'm' #used to mark the end point of the major diameter + newMajor = getPositionArgument($script_args, $major, default: cursorAsDigPos()) + noMoreArguments($script_args) + + requireOriginZLevel() + + setMajor(newMajor.x, newMajor.y, newMajor.z) # need to refactor setMajor + + writeLuaPos("major", $major) + + + + stdout "Now move the cursor to the minor axis radius (extent) and call ellipse3p" + when 'resetz', 'setz' + z = df.cursor.z # default + + if args[0] then + z = getPosComponentArgument(args, makeDefaultPosMap(origin), :z) # only really need the z-component from origin for default end - when 'major' #digshape major - #used to mark the end point of the major diameter - #$major = df.cursor - $majorx = df.cursor.x - $majory = df.cursor.y - $majorz = df.cursor.z - if df.cursor.z == $originz then - markOrigin($majorx, $majory, $majorz) - puts " Now move the cursor to the minor axis radius (extent) and call ellipse3p" - else - puts " Error: origin and target must be on the same z level" - throw :script_finished + noMoreArguments($script_args) + + setOrigin($origin.x, $origin.y, z) + if $major then + setMajor($major.x, $major.y, z) end - when 'ellipse3p' #digshape ellipse3p [filled] [digmode] - if df.cursor.z == $originz then - if $majorx == nil then - puts " Error: Set a point for the end of the major axis with the cursor and 'digshape major'" - throw :script_finished - end - - filled = false - case argument1 - when 'filled'; filled = true - when 'hollow'; filled = false - when 'true'; filled = true - when 'false'; filled = false - when 't'; filled = true - when 'f'; filled = false - when 'y'; filled = true - when 'n'; filled = false - end - - if filled then - puts " Filled not yet supported for 3p ellipses." - filled = false - end + when 'ls', 'status' + stdout "origin: #{$origin != nil ? $origin.to_s : ''}" + stdout "major : #{$major != nil ? $major.to_s : ''}" + stdout "cursor: #{cursorAsDigPos().to_s}" + + when 'line', 'l' + digMode = getDigModeArgument($script_args) + noMoreArguments($script_args) - dig = getDigMode(argument1) #check argument 1 for dig instructions - if argument2 then # if argument 2 is present, look at that for dig instructions - dig = getDigMode(argument2) - end + requireOriginZLevel() - drawEllipse($originx, $originy, $originz, $majorx, $majory, $majorz, df.cursor.x, df.cursor.y, df.cursor.z, filled = filled, digMode = dig, mode = 'axis') - else - puts " Error: all control points must be on the same z level" - throw :script_finished + drawLine($origin.x, $origin.y, $origin.z, df.cursor.x, df.cursor.y, df.cursor.z, digMode) + + when 'ellipse', 'e' #digshape ellipse [filled] [digmode] + filled = getFilledArgument($scripts_args) + digMode = getDigModeArgument($script_args) + noMoreArguments($script_args) + + requireOriginZLevel() + + drawEllipse($origin.x, $origin.y, $origin.z, df.cursor.x, df.cursor.y, df.cursor.z, x2=nil, y2=nil, z2=nil, filled=filled, digMode=digMode, mode='bbox') # fixme: default arguments should be colon not equals + + when 'circle2p', 'circle', 'c' #digshape circle2p [filled] [digmode] + filled = getFilledArgument($script_args) + digMode = getDigModeArgument($script_args) #check argument 1 for dig instructions + noMoreArguments($script_args) + + requireOriginZLevel() + + drawEllipse($origin.x, $origin.y, $origin.z, df.cursor.x, df.cursor.y, df.cursor.z, x2=nil, y2=nil, z2=nil, filled=filled, digMode=digMode, mode='diameter') # fixme: default arguments should be colon not equals + + when 'ellipse3p', 'e3p' #digshape ellipse3p [filled] [digmode] + filled = getFilledArgument($script_args) + digMode = getDigModeArgument($script_args) + noMoreArguments($script_args) + + requireOriginZLevel(msg:"All control points must be on the same z level") + requireMajor() + + if filled then + stdout "Filled not yet supported for 3p ellipses." + filled = false end - when 'bez' #digshape bez + + drawEllipse($origin.x, $origin.y, $origin.z, $major.x, $major.y, $major.z, df.cursor.x, df.cursor.y, df.cursor.z, filled=filled, digMode=digMode, mode='axis') # fixme: default arguments should be colon not equals + + when 'bezier', 'bez', 'b' #digshape bezier [weight] digmode] #use origin and major as endpoints, cursor as curve shaper - if df.cursor.z == $originz then - if $majorx == nil then - puts " Error: Set an endpoint for the curve with the cursor and 'digshape major'" - throw :script_finished - end - - weight = argument1.to_f - if weight = 0 then - weight = 1.5 - end - - dig = getDigMode(argument1) #check argument 1 for dig instructions - if argument2 then # if argument 2 is present, look at that for dig instructions - dig = getDigMode(argument2) - end - - plotQuadRationalBezier($originx, $originy, $originz, df.cursor.x, df.cursor.y, df.cursor.z, $majorx, $majory, $majorz, weight, dig) - else - puts " Error: all control points must be on the same z level" - throw :script_finished - end - when 'polygon' - if not argument1 then - puts " Must supply a polygon n-sides parameter" - throw :script_finished - else - n = argument1.to_i - dig = getDigMode(argument2) - if argument3 then - dig = getDigMode(argument3) - end - apothem=false; - case argument2 - when 'apothem'; apothem=true - when 'radius'; apothem=false - when 'a'; apothem=true - when 'r'; apothem=false - when 't'; apothem=true - when 'f'; apothem=false - when 'y'; apothem=true - when 'n'; apothem=false - end - if df.cursor.z == $originz then - drawPolygon($originx, $originy, $originz, df.cursor.x, df.cursor.y, df.cursor.z, n, apothem, dig) - else - puts " Error: origin and target must be on the same z level" - throw :script_finished - end - end - when 'star' # star N [SKIP=2] [DIGMODE] - if not argument1 then - puts " Must supply a star n-sides parameter" - throw :script_finished - else - dig = getDigMode(argument2) - if argument3 then - dig = getDigMode(argument3) - end - n = argument1.to_i - skip = Integer(argument2) rescue 2 + weight = getFloatArgument($script_args, default: 1.5, type: "bezier weight") + digMode = getDigModeArgument($script_args) #check argument 1 for dig instructions + noMoreArguments($script_args) + + requireOriginZLevel(msg:"All control points must be on the same z level") + requireMajor() + + plotQuadRationalBezier($origin.x, $origin.y, $origin.z, df.cursor.x, df.cursor.y, df.cursor.z, $major.x, $major.y, $major.z, weight, digMode) - if df.cursor.z == $originz then - drawStar($originx, $originy, $originz, df.cursor.x, df.cursor.y, df.cursor.z, n, skip, dig) - else - puts " Error: origin and target must be on the same z level" - throw :script_finished - end + when 'polygon', 'p' #digshape polygon [sides] [apothem/radius] [digMode] # apothem is default + sides = getIntegerArgument($script_args, type: "polygon n-sides") + + apothem = false # custom argument parse + case $script_args[0] + when 'apothem', 'a', 't', 'true', 'y', 'yes'; apothem=true; $script_args.delete_at(0) + when 'radius', 'r', 'f', 'false', 'n', 'no'; apothem=false; $script_args.delete_at(0) end - when 'downstair' - if not argument1 then - puts " Must supply a depth parameter" - throw :script_finished - else - depth = argument1.to_i - if depth <= 0 then - puts " Depth must be an integer greater than zero" - throw :script_finished - else - digKeupoStair(df.cursor.x, df.cursor.y, df.cursor.z, depth) - end + + digMode = getDigModeArgument($script_args) + + noMoreArguments($script_args) + + requireOriginZLevel() + + drawPolygon($origin.x, $origin.y, $origin.z, df.cursor.x, df.cursor.y, df.cursor.z, sides, apothem, digMode) + + when 'star', 's' #digshape star N [skip=2] [digMode] + n = getIntegerArgument($script_args, type: "star n-sides") + + skip = getIntegerArgument($script_args, default: 2, type: "skip") + digMode = getDigModeArgument($script_args) + + noMoreArguments($script_args) + + requireOriginZLevel() + + drawStar($origin.x, $origin.y, $origin.z, df.cursor.x, df.cursor.y, df.cursor.z, n, skip, digMode) + when 'spiral' + coils=getIntegerArgument($script_args, default: 2, type: "number of coils") + chord=getIntegerArgument($script_args, default: 1, type: "distance between points") + digMode = getDigModeArgument($script_args) + noMoreArguments($script_args) + + drawSpiral($origin.x, $origin.y, $origin.z, df.cursor.x, df.cursor.y, df.cursor.x, coils, chord, digMode) + when 'keupo', 'stairs', 'downstairs', 'downstair' #digshape keupo depth + depth = getIntegerArgument($script_args, type: "depth") + + noMoreArguments($script_args) + + if depth <= 0 then + userSucks "Depth must be an integer greater than zero" end - when 'flood' - maxArea = argument1.to_i - if maxArea == 0 then maxArea = 10000 end - dig = getDigMode(argument1) #check argument 1 for dig instructions - if argument2 then # if argument 2 is present, look at that for dig instructions - dig = getDigMode(argument2) - end + digKeupoStair(df.cursor.x, df.cursor.y, df.cursor.z, depth) + when 'flood', 'f' + maxArea = getIntegerArgument($script_args, default: 10000, type: "maximum flood area") + digMode = getDigModeArgument($script_args) + + noMoreArguments($script_args) - t=df.map_tile_at(df.cursor.x, df.cursor.y, df.cursor.z) - targetDig = t.designation.dig #we will only fill the designation type under the cursor. + tile = df.map_tile_at(df.cursor.x, df.cursor.y, df.cursor.z) + targetDig = tile.designation.dig #we will only fill the designation type under the cursor. - if not t.designation.dig==:No then - if dig2enum(dig) == t.designation.dig then - puts " Error: floodfill must be centered on an undesignated/matching tile." - throw :script_finished + if targetDig != :No then + if targetDig == digMode2enum(digMode) then + userSucks "Floodfill must be centered on an undesignated/matching tile." end end - - floodfill(df.cursor.x, df.cursor.y, df.cursor.z, targetDig, dig, maxArea) - throw :script_finished + floodfill(df.cursor.x, df.cursor.y, df.cursor.z, targetDig, dig, maxArea) when 'undo' unDig() - when 'spiral' - if not argument1 then - puts " Must supply a coils parameter" - throw :script_finished - else - coils = argument1.to_i - chord = 2 - if argument2 then - chord = argument2.to_i - end - - dig = getDigMode(argument3) - - drawSpiral($originx, $originy, $originz, df.cursor.x, df.cursor.y, df.cursor.x, coils, chord, dig) - end else - puts " Error: Invalid command" - throw :script_finished -end + userSucks "Invalid command" +end \ No newline at end of file diff --git a/gui/digshape.lua b/gui/digshape.lua new file mode 100644 index 0000000..ce07a7b --- /dev/null +++ b/gui/digshape.lua @@ -0,0 +1,237 @@ +--designating tool + +--[====[ + +gui/digshape +=========== +gui front-end for digshape.rb + +]====] + +local utils = require "utils" +local gui = require "gui" +local guidm = require "gui.dwarfmode" +local dialog = require "gui.dialogs" + +DigshapeUI = defclass(DigshapeUI, guidm.MenuOverlay) + +DigshapeUI.ATTRS { + state = "preview", + activeDesgination = 'd', + currentCommand = 'circle hollow @', + currentOutput = {}, + currentError = {}, + currentDig = {} + -- default properties for self here +} + + +local digButtons={ + {key='d', symbol=" ", text="Mine"}, + {key='i', symbol="X", text="U/D Stair"}, + {key='h', symbol="_", text="Channel"}, + {key='r', symbol=30, text="Up Ramp"}, + {key='j', symbol=">", text="Down Stair"}, + {key='u', symbol="<", text="Up Stair"}, + {key='x', symbol=" ", text="Remove Designation"}, +} + +local digModeToButton = {} +for _, data in pairs(digButtons) do + data.keybind = ("CUSTOM_%s"):format(data.key:upper()) + digModeToButton[data.key] = data +end + +local buttons = { + {key="p", text="Set digshape command", callback=function(self) + dialog.showInputPrompt("Set digshape command", "Enter a digshape command", COLOR_WHITE, self.currentCommand, function(result) + self.currentCommand=result + self:runCurrentCommand(true) + end) + end}, + {key="o", text="Set origin", callback=function(self) + dfhack.run_command_silent("digshape lua origin") + self:runCurrentCommand(true) + end}, + {key="m", text="Set major", callback=function(self) + dfhack.run_command_silent("digshape lua major") + self:runCurrentCommand(true) + end}, + {key="SELECT", keybind="SELECT", text="Execute command", callback=function(self) + self:runCurrentCommand(false) + end}, + {key="z", text="Undo digshape command", callback=function(self) + dfhack.run_command("digshape undo") + end} +} +for _, data in pairs(buttons) do + data.keybind = data.keybind or ("CUSTOM_%s"):format(data.key:upper()) +end + +local lastX = df.global.cursor.x +local lastY = df.global.cursor.y +local lastZ = df.global.cursor.z + +function DigshapeUI:runCurrentCommand(preview) + local command = ("digshape lua %s%s"):format(preview and "preview " or "", self.currentCommand):gsub("@", self.activeDesgination) + --print(("command='%s'"):format(command)) + local output = dfhack.run_command_silent(command) + self.currentOutput = {} + self.currentError = {} + self.currentDig = {} + self.origin = nil + self.major = nil + --print("output=", output) + for line in output:gmatch("[^\r\n]+") do + messageType = line:match("^([^:]+):") + if messageType == "msg" then + messageContents = line:match("^msg:(.*)$") + table.insert(self.currentOutput, messageContents) + elseif messageType == "err" then + messageContents = line:match("^err:(.*)$") + table.insert(self.currentError, messageContents) + elseif messageType == "dig" then + digMode, x, y, z = line:match("^dig:([^:]+):([^:]+):([^:]+):([^:]+)") + table.insert(self.currentDig, {digMode=digMode, x=tonumber(x), y=tonumber(y), z=tonumber(z), symbol=digModeToButton[digMode].symbol}) + elseif messageType == "pos" then + posname, x, y, z = line:match("^pos:([^:]+):%(([^,]+),([^,]+),([^,]+)%)") + x, y, z = tonumber(x), tonumber(y), tonumber(z) + if posname == "origin" then + self.origin = xyz2pos(x, y, z) + elseif posname == "major" then + self.major = xyz2pos(x, y, z) + end + + else + print("unhandled output:", line) + end + end +end + +function DigshapeUI:init() + self.saved_mode = df.global.ui.main.mode + df.global.ui.main.mode=df.ui_sidebar_mode.LookAround + self:runCurrentCommand(true) +end + +function DigshapeUI:onDestroy() + df.global.ui.main.mode = self.saved_mode +end + +local function paintMapTile(dc, vp, cursor, pos, ...) + if not same_xyz(cursor, pos) then + local stile = vp:tileToScreen(pos) + if stile.z == 0 then -- FIXME: reduce lag by increasing overlay + dc:map(true):seek(stile.x,stile.y):char(...):map(false) + end + end +end + + +function DigshapeUI:renderOverlay() + local vp=self:getViewport() + local dc = gui.Painter.new(self.df_layout.map) + local visible = gui.blink_visible(500) + + local cursorX, cursorY, cursorZ = df.global.cursor.x, df.global.cursor.y, df.global.cursor.z + if lastX ~= cursorX or lastY ~= cursorY or lastZ ~= cursorZ then + lastX, lastY, lastZ = cursorX, cursorY, cursorZ + self:runCurrentCommand(true) + end + + for _, dig in ipairs(self.currentDig) do + paintMapTile(dc, vp, df.global.cursor, xyz2pos(dig.x, dig.y, dig.z), dig.symbol, COLOR_BLACK, self.activeDesgination=='x' and COLOR_RED or COLOR_BROWN) + end + + if self.origin then + paintMapTile(dc, vp, df.global.cursor, self.origin, '+', COLOR_YELLOW) + end + if self.major then + paintMapTile(dc, vp, df.global.cursor, self.major, '+', COLOR_LIGHTGREEN) + end + + +end + +function DigshapeUI:onRenderBody(dc) + self:renderOverlay() + + dc:clear():seek(1,1):pen(COLOR_WHITE):string("Digshape - Main menu") + dc:seek(1,3) + if true or self.state=="preview" then + for _, data in pairs(digButtons) do + builder = dc:key_string(data.keybind, data.text, self.activeDesgination==data.key and COLOR_WHITE or COLOR_GREY):newline(1) + if data.key=='x' then + builder:newline(1) + end + end + for _, data in pairs(buttons) do + builder = dc:key_string(data.keybind, data.text, COLOR_GREY):newline(1) + if data.key=='m' then + builder:newline(1) + end + end + + + --[[ dc:key_string("CUSTOM_S", "Set Brush",COLOR_GREY) + dc:newline():newline(1) + dc:key_string("CUSTOM_H", "Flip Horizontal",COLOR_GREY):newline(1) + dc:key_string("CUSTOM_V", "Flip Vertical",COLOR_GREY):newline(1) + dc:key_string("CUSTOM_R", "Rotate 90",COLOR_GREY):newline(1) + dc:key_string("CUSTOM_T", "Rotate -90",COLOR_GREY):newline(1) + dc:key_string("CUSTOM_G", "Cycle Corner",COLOR_GREY):newline(1) + dc:key_string("CUSTOM_I", "Invert",COLOR_GREY):newline(1) + dc:key_string("CUSTOM_C", "Convert to...",COLOR_GREY):newline(1) + dc:newline(1) + dc:key_string("CUSTOM_E", (self.option=="erase" and "Erasing" or "Erase"),self.option=="erase" and COLOR_RED or COLOR_GREY):newline(1) --make red + dc:key_string("CUSTOM_X", (self.option=="construction" and "Removing" or "Remove").." Constructions",self.option=="construction" and COLOR_GREEN or COLOR_GREY):newline(1) --make red + dc:newline():newline(1) + dc:key_string("CUSTOM_B", "Blink Brush",self.blink and COLOR_WHITE or COLOR_GREY):newline(1) + dc:newline() ]] + end + + dc:newline():newline(1):key_string("LEAVESCREEN", "Back") +end + + +function DigshapeUI:onInput(keys) + if df.global.cursor.x==-30000 then + local vp=self:getViewport() + df.global.cursor=xyz2pos(math.floor((vp.x1+math.abs((vp.x2-vp.x1))/2)+.5),math.floor((vp.y1+math.abs((vp.y2-vp.y1)/2))+.5), vp.z) + return + end + for k,v in pairs(keys) do + if k:match("^A_MOVE_") then + self.refresh = 1 + end + end + if true or self.state=="preview" then + for _, data in ipairs(digButtons) do + if keys[data.keybind] then + self.activeDesgination = data.key + self:runCurrentCommand(true) + end + end + for _, data in ipairs(buttons) do + if keys[data.keybind] then + data.callback(self) + end + end + if keys.SELECT then + --self:pasteBuffer(copyall(df.global.cursor)) + end + end + + if keys.LEAVESCREEN then + self:dismiss() + elseif self:propagateMoveKeys(keys) then + return + end +end + +if not (dfhack.gui.getCurFocus():match("^dwarfmode/Default") or dfhack.gui.getCurFocus():match("^dwarfmode/Designate") or dfhack.gui.getCurFocus():match("^dwarfmode/LookAround"))then + qerror("This screen requires the main dwarfmode view or the designation screen") +end + +local list = DigshapeUI{state="mark", blink=false,cull=true} +list:show() \ No newline at end of file From 96eae8ce8b767753bcdf50ee9da4bd69dcb10297 Mon Sep 17 00:00:00 2001 From: Quatch Date: Thu, 25 Mar 2021 22:05:15 -0400 Subject: [PATCH 04/12] Tidying and commenting Regrouped functions, added more comments. --- digshape.rb | 519 +++++++++++++++++++++++++++++++++++----------------- 1 file changed, 356 insertions(+), 163 deletions(-) diff --git a/digshape.rb b/digshape.rb index 7fc8013..3fb2f60 100644 --- a/digshape.rb +++ b/digshape.rb @@ -3,6 +3,7 @@ digshape ======= +digshape allows the creation of a number of repetative geometric designations. The script can be called manually, or through gui/digshape. Commands that do not require a set origin: @@ -11,22 +12,25 @@ To undo the previous command (restoring designation): digshape undo + To flood fill with a designation, overwriting ONLY the designation under the cursor: + digshape flood + Commands that require an origin to be set: To set the origin for drawing: digshape origin - To draw to the target point: + To draw to the cursor: digshape line To draw an ellipse using the origin and target as a bounding box: digshape ellipse (filled? [default: false]) - To draw an ellipse using the origin and target as a major axis, and the cursor as the length of the semiminor axis: - digshape major (to set the major axis endpoint) + To draw an ellipse using the origin and target as a major axis, and the cursor as the length of the semiminor axis (aka width [dist from cursor to line]): + digshape major (to set the major axis endpoint [the length]) digshape ellipse3p - To draw a circle using an arbitrary diameter: + To draw a circle using an arbitrary diameter (gives slightly different results to digcircle): digshape circle2p To draw a 3 pt bezier curve, with an arbitrary float for weighting the sharpness: @@ -35,36 +39,92 @@ To draw a polygon using the origin as the center and the cursor as the radius|apothem (radius) digshape polygon [radius|apothem] [digMode] - - To flood fill with a designation, overwriting ONLY the designation under the cursor: - digshape flood - - To move all of the markers to the current z level: - digshape resetz + + To draw a star using the origin as the center and the cursor as the radius|apothem (radius) + digshape star [skip] [digMode] To draw an Archimedean spiral (coils - number of coils, chord - distance between points): digshape spiral + To move all of the markers to the current z level: + digshape resetz All commands accept a digging designation mode as a single character argument [dujihrx], otherwise will default to 'd' +=end + + + + + + +=begin +============ SCRIPT DESCRIPTION ============ +Digshape operates directly on the current mapstate. An undo buffer is maintained for the last-run command, and for the markers. + +This file is broken into segments. +=GUI interfacing + code required to allow headless operation through gui/digshape. + +=Utility functions + generic helper functions for script -TODO: mark origin should not change the digging designation, ellipse cleanup should restore not clear it. +=Data structures + structure to hold coordinates, and to interpret the designations. +=Cursor functions + code to interact with the map (df(hack)). Get, set, undo. + +=Shape functions + functions to draw geometries. + +=Script control + print help, functions to parse arguments, untap, upkeep. + +=Commands + user-callable interactions with digshape. Each command is responsible for getting arguments it needs, calling the functions to make it's geometry, and plotting them to the map (frequently a side-effect of the geometry function). + +============ KNOWN BUGS ============ + BUG: "digshape polygon 3 r" uses both 'radius' and digmode:'r' + BUG: "digshape polygon 2 apothem" does not work, but higher numbers do. + + + +============ TODO ============ + TODO: mark origin should not change the digging designation, ellipse cleanup should restore not clear it. + TODO: replace text dig designations with dfhack enums + TODO: convert digshape to lua script + TODO: just always use the current Z level. (ensure undo) + TODO: rename control points to A,B,C,... for more generality and faster discussion. Maybe origin+ABCD...? [Origin, Cursor, A,B,C,D,...] + TODO: add marker mode/toggle marker designation, smooth, engrave, carveFortification + + + + +============ FEATURE IDEAS ============ + IDEA: Pixel fonts (size) + IDEA: Gradient fill. (2pt box, 1pt midpoint, arg: direction[NSEW,diags,star,pit,rings,etc]) + IDEA: 3d shapes (eg platonic solids) + + IDEA: circle3p (given any 3p) (((Ali Sheikhpour (https://math.stackexchange.com/users/707123/ali-sheikhpour), Get the equation of a circle when given 3 points, URL (version: 2021-01-26): https://math.stackexchange.com/q/4000949))) + IDEA: arc 3p (as circle3p but only draw inside bbox) + IDEA: default digmode is whatever is selected currently in the active df:designationMode screen + IDEA: add diagonal adjacency to floodfill as an option =end -DigPos = Struct.new(:x, :y, :z) do - def to_s - return "(#{x},#{y},#{z})" - end - def clone - return DigPos.new(x,y,z) - end -end -def cursorAsDigPos() - return DigPos.new(df.cursor.x, df.cursor.y, df.cursor.z) -end -# really nasty hack so that lua can use digshape + + + + + + +=begin +======================== DIGSHAPE GUI interfacing +really nasty hack so that lua can use digshape. + +GUI allows for 'preview' which returns the points digshape would dig, but does not modify the map. Printing and errors are redirected. +=end + $isLuaMode = $script_args[0] == "lua" $isPreviewOnly = false @@ -83,32 +143,18 @@ def writeLuaPos(name, digPos) # pos:::: end end -def setOrigin(x, y, z) # sets the origin and marks if it iff we are in console. cleans up last mark too. - $origin = DigPos.new(x,y,z) - # the rest is just really complicated logic to mark the origin if the user is using the console version - # it also has to play well with lua - if $oldOrigin then - oldTile = df.map_tile_at($oldOrigin.x, $oldOrigin.y, $oldOrigin.z) - oldTile.dig($oldOriginDesignation) if oldTile.shape_basic == $oldOriginShape && oldTile.designation.dig == digMode2enum('d') - end - if not $isLuaMode then - $oldOrigin = $origin.clone() - newOriginTile = df.map_tile_at($origin.x, $origin.y, $origin.z) - $oldOriginDesignation = newOriginTile.designation.dig - $oldOriginShape = newOriginTile.shape_basic - digAt($origin.x, $origin.y, $origin.z, 'd', buffer: false) - else - $oldOrigin = nil # don't undo our origins if we are in lua mode - end -end -def setMajor(x, y, z) - $major = DigPos.new(x,y,z) -end + + +=begin +======================== UTILITY FUNCTIONS +=end + def stdout(msg) + # print a message to the console if $isLuaMode == false then puts msg else @@ -116,7 +162,8 @@ def stdout(msg) end end -def stderr(msg) # write an error mesage +def stderr(msg) + # print an error message to the console if $isLuaMode == false then puts " Error: "+msg else @@ -124,51 +171,66 @@ def stderr(msg) # write an error mesage end end -def scriptError(msg) # call this when you reach corner cases / the fault perhaps isn't the user +def scriptError(msg) + # call this when you reach corner cases / the fault perhaps isn't the user. Script terminates. stderr(msg) raise "oopsie! script errored! ;)" end -def userSucks(msg) # call this when we don't like the user's input +def userSucks(msg) + # call this when we don't like the user's input. Script may continue. stderr(msg) throw :script_finished end -def undo() # BUG! Does not keep track of Z levels - #Execute one level of undo. - #z level is presumed to be the current. - # todo, have multiple levels of undo / redo - i=$digBufferX.length - newBufferX = [] - newBufferY = [] - newBufferZ = [] # redundant, i.e. always the same most of the time, but needed so that we use it as a pointer for digAt. Also supports digging multi dimenisional shapes - newBufferD = [] - while i > 0 do - x=$digBufferX.pop - y=$digBufferY.pop - z=$digBufferZ.pop - d=$digBufferD.pop - digAt(x,y,z, enum2digMode(d), buffer: true, bufferX: newBufferX, bufferY: newBufferY, bufferZ: newBufferZ, bufferD: newBufferD) - i = i-1 + + + + + + +=begin +======================== DATA STRUCTURES +=end + + +DigPos = Struct.new(:x, :y, :z) do + #Holds a 3d coordinate + def to_s + return "(#{x},#{y},#{z})" + end + def clone + return DigPos.new(x,y,z) end - #clear buffer for next dig - $digBufferX = newBufferX - $digBufferY = newBufferY - $digBufferZ = newBufferZ - $digBufferD = newBufferD end +#Control points: + #$origin + #$major + #$cursor def clearDigBuffer() - #clear buffer for next dig, or initialize it's existance on first run. + #$digBuffer* is a set of global arrays containing the 3d coordinates and their prior dig designations. + + #clear buffer for next dig, or initialize if empty. $digBufferX=[] $digBufferY=[] $digBufferZ=[] $digBufferD=[] end + +def getDigMode(digMode = 'd') +#TODO just integrate this into getDigModeArgument. + if ['d', 'u', 'j', 'i', 'h', 'r', 'x'].include? digMode then + return digMode + end + return 'd' +end + + def digMode2enum(digMode) #this function turns a digmode into the appropriate enum for easier comparison on tile reading (eg floodfill.) case digMode #from https://github.com/DFHack/scripts/blob/master/digfort.rb @@ -200,6 +262,78 @@ def enum2digMode(digEnum) end end + + + +=begin +======================== CURSOR FUNCTIONS +=end + + +def cursorAsDigPos() + #returns current cursor position as a DigPos + return DigPos.new(df.cursor.x, df.cursor.y, df.cursor.z) +end + + + + +def setOrigin(x, y, z) + # sets the origin and marks if it iff we are in console. cleans up last mark too. + #TODO: make 'controlpoints' an array indexed by name, so that all can be operated on in the same way. + $origin = DigPos.new(x,y,z) + # the rest is just really complicated logic to mark the origin if the user is using the console version + # it also has to play well with lua + + if $oldOrigin then + oldTile = df.map_tile_at($oldOrigin.x, $oldOrigin.y, $oldOrigin.z) + oldTile.dig($oldOriginDesignation) if oldTile.shape_basic == $oldOriginShape && oldTile.designation.dig == digMode2enum('d') + end + if not $isLuaMode then + $oldOrigin = $origin.clone() + newOriginTile = df.map_tile_at($origin.x, $origin.y, $origin.z) + $oldOriginDesignation = newOriginTile.designation.dig + $oldOriginShape = newOriginTile.shape_basic + + digAt($origin.x, $origin.y, $origin.z, 'd', buffer: false) + else + $oldOrigin = nil # don't undo our origins if we are in lua mode + end +end + +def setMajor(x, y, z) + # Assigns the control point 'major' to the current cursor location + $major = DigPos.new(x,y,z) +end + + + +def undo() + #Execute one level of undo. + #z level is presumed to be the current. + #BUG: Does not keep track of Z levels + #TODO: have multiple levels of undo / redo + i=$digBufferX.length + newBufferX = [] + newBufferY = [] + newBufferZ = [] # redundant, i.e. always the same most of the time, but needed so that we use it as a pointer for digAt. Also supports digging multi dimenisional shapes + newBufferD = [] + while i > 0 do + x=$digBufferX.pop + y=$digBufferY.pop + z=$digBufferZ.pop + d=$digBufferD.pop + digAt(x,y,z, enum2digMode(d), buffer: true, bufferX: newBufferX, bufferY: newBufferY, bufferZ: newBufferZ, bufferD: newBufferD) + i = i-1 + end + #clear buffer for next dig + $digBufferX = newBufferX + $digBufferY = newBufferY + $digBufferZ = newBufferZ + $digBufferD = newBufferD +end + + def isDigPermitted(digMode, tileShape) # can we dig on this tile? @@ -215,6 +349,8 @@ def isDigPermitted(digMode, tileShape) end def digAt(x, y, z, digMode = 'd', buffer: true, bufferX: $digBufferX, bufferY: $digBufferY, bufferZ: $digBufferZ, bufferD: $digBufferD) + #Commit designation@coords to the map, opt save current value there to the buffer for undo. + tile = df.map_tile_at(x, y, z) # check if the tile returned is valid, ignore if its not (out of bounds, air, etc) @@ -242,8 +378,16 @@ def digAt(x, y, z, digMode = 'd', buffer: true, bufferX: $digBufferX, bufferY: $ -# https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm + + +=begin +======================== SHAPES FUNCTIONS +=end + + def drawLineLow(x0, y0, z0, x1, y1, z1, digMode = 'd') + # Helper function for drawLine. + # Uses: https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm dx = x1 - x0 dy = y1 - y0 yi = 1 @@ -268,6 +412,8 @@ def drawLineLow(x0, y0, z0, x1, y1, z1, digMode = 'd') end def drawLineHigh(x0, y0, z0, x1, y1, z1, digMode = 'd') + # Helper function for drawLine. + # Uses: https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm dx = x1 - x0 dy = y1 - y0 xi = 1 @@ -291,6 +437,8 @@ def drawLineHigh(x0, y0, z0, x1, y1, z1, digMode = 'd') end def drawLine(x0, y0, z0, x1, y1, z1, digMode = 'd') + # Draw a straight, line between two points. + # Uses: https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm if (y1 - y0).abs < (x1 - x0).abs then if x0 > x1 then drawLineLow(x1, y1, z1, x0, y0, z0, digMode) @@ -307,11 +455,14 @@ def drawLine(x0, y0, z0, x1, y1, z1, digMode = 'd') end def plotQuadRationalBezierSeg(x0, y0, z0, x1, y1, z1, x2, y2, z2, w, digMode = 'd') - #/* plot a limited rational Bezier segment, squared weight */ - #http://members.chello.at/easyfilter/bresenham.pdf listing 12 + # Helper function for plotQuadRationalBezier, draws one portion of the curve. + +=begin + /* plot a limited rational Bezier segment, squared weight */ + Source: http://members.chello.at/easyfilter/bresenham.pdf listing 12 #p0:origin, p1:weight, p2:termination #w is the weighting. "For w =1 the curve is a parabola, for w < 1 the curve is an ellipse, for w = 0 the curve is a straight line and for w>1 the curve is a hyperbola. The weights are normally assumed to be all positive." - +=end x0 = x0.floor #start with integer locations. Original code stores in int, so this is implicit. x1 = x1.floor x2 = x2.floor @@ -423,8 +574,9 @@ def plotQuadRationalBezierSeg(x0, y0, z0, x1, y1, z1, x2, y2, z2, w, digMode = ' end def plotQuadRationalBezier(x0, y0, z0, x1, y1, z1, x2, y2, z2, w=1.5, digMode = 'd') - #http://members.chello.at/easyfilter/bresenham.pdf listing 11 - ## plot any quadratic rational Bezier curve */ + #Draw a bezier between origin[p0] and control point[p2], pulled out towards cursor[p1] (by a weighting of 'w') + + # Source: http://members.chello.at/easyfilter/bresenham.pdf listing 11: /* plot any quadratic rational Bezier curve */ x = x0 - 2 * x1 + x2 y = y0 - 2 * y1 + y2 @@ -511,18 +663,20 @@ def plotQuadRationalBezier(x0, y0, z0, x1, y1, z1, x2, y2, z2, w=1.5, digMode end def plotRotatedEllipse(x, y, z, a, b, angle, digMode='d') - ## plot ellipse rotated by angle (radian) */ - #taken from: http://members.chello.at/easyfilter/bresenham.pdf listing 13. Explicitly released without copyright - #Note: most of this function deals with the ellipse at the origin. Translation to coordinates is at final call. + # Helper function for drawEllipse(). Draw an ellipse(center, major len, minor len) rotated by angle (radian) + +=begin + Source: http://members.chello.at/easyfilter/bresenham.pdf listing 13. Explicitly released without copyright + Note: most of this function deals with the ellipse at the origin. Translation to coordinates is at final call. - #x,y is the coodinates of the center - #a is __SEMI__major length - #b is __SEMI__minor length - #angle (radians), prob measured CCW from east + x,y is the coodinates of the center + a is __SEMI__major length + b is __SEMI__minor length + angle (radians), prob measured CCW from east - #A far more readable paper on plotting rotated ellipses (no pseudocode): http://www.crbond.com/papers/ell_alg.pdf - #Another paper on rasterizing 2d primitives: https://cs.brown.edu/research/pubs/theses/masters/1989/dasilva.pdf - + A far more readable paper on plotting rotated ellipses (no pseudocode): http://www.crbond.com/papers/ell_alg.pdf + Another paper on rasterizing 2d primitives: https://cs.brown.edu/research/pubs/theses/masters/1989/dasilva.pdf +=end angle = -angle #deal with -y axis. xd = a * a @@ -558,7 +712,7 @@ def plotRotatedEllipseRect(x0, y0, z0, x1, y1, zd, digMode='d') if (zd == 0) #Special case: no rotation. Use standard method. /* looks nicer */ #this should never be reached, as we call this from the regular ellipse function. - stdout "zd=0 degenerate case" + stdout("zd=0 degenerate case") drawEllipse(x0,y0,z0, x1,y1,z0) return end @@ -569,7 +723,7 @@ def plotRotatedEllipseRect(x0, y0, z0, x1, y1, zd, digMode='d') end if not(w <= 1.0 && w >= 0.0) then #/* limit angle to |zd|<=xd*yd */ - scriptError "Limit angle to |zd|<=xd*yd" + scriptError("Limit angle to |zd|<=xd*yd") end ## snap xe,ye to int */ @@ -584,6 +738,9 @@ def plotRotatedEllipseRect(x0, y0, z0, x1, y1, zd, digMode='d') end def drawEllipse(x0, y0, z0, x1, y1, z1, x2=nil, y2=nil, z2=nil, filled = false, digMode = 'd', mode = 'bbox') + #Draw an ellipse, using the current control points, in the method specified by mode. + +=begin # A Fast Bresenham Type Algorithm For Drawing Ellipses http://homepage.smc.edu/kennedy_john/belipse.pdf (https://www.dropbox.com/s/3q89g566u115g3q/belipse.pdf?dl=0) # also adapted from https://github.com/teichgraf/WriteableBitmapEx/blob/master/Source/WriteableBitmapEx/WriteableBitmapShapeExtensions.cs used under the MIT license @@ -591,6 +748,13 @@ def drawEllipse(x0, y0, z0, x1, y1, z1, x2=nil, y2=nil, z2=nil, filled = false, #p1 [xyz]: termination of major axis; OR the other corner of the bbox #p2 [xyz]: the extent (not a point nessisarily on the minor axis..) of the minor _radius_; aka, a point on the bounding box long side that will be used to determine the length of the short side. + #mode ['bbox']: + -'diameter': make a circle given 2p as the diameter + -'axis': make an ellipse along the line [origin, major], with the cursor setting the width. width is the distance from the cursor to the line. + -'bbox': generate an ellipse to fit entirely within the bounding box of [origin, cursor] + -IDEA: '5p': given 5p draw an ellipse that fits. +=end + xl = [x0, x1].min # find left edge xr = [x0, x1].max # find right edge yb = [y0, y1].min # find lower edge @@ -762,6 +926,7 @@ def drawEllipse(x0, y0, z0, x1, y1, z1, x2=nil, y2=nil, z2=nil, filled = false, end def digKeupoStair(x, y, z, depth) + #Dig an X of updown stairs (corners and center of a 3x3) centered on cursor, down a number of zlevels. iz = z digAt(x, y, iz, 'j') digAt(x - 1, y + 1, iz, 'j') @@ -778,15 +943,9 @@ def digKeupoStair(x, y, z, depth) end end -def getDigMode(digMode = 'd') - if ['d', 'u', 'j', 'i', 'h', 'r', 'x'].include? digMode then - return digMode - end - return 'd' -end - def drawPolygon(x0, y0, z0, x1, y1, z1, n = 3, apothem=false, digMode = 'd') - # if you dig a 2-gon (aka a line) it always passes through the origin so it's still convienient / useful + #Draw a polygon centered on origin with cursor at (apothem==T: midpoint of a side, ==F: vertex) + # if you dig a 2-gon (aka a line) it always passes through the origin so it's still convienient / useful. In apothem==T this makes the origin the midpoint of the drawn line. xOffset = x1 - x0 yOffset = y1 - y0 @@ -817,6 +976,7 @@ def drawPolygon(x0, y0, z0, x1, y1, z1, n = 3, apothem=false, digMode = 'd') end def drawStar(x0, y0, z0, x1, y1, z1, n = 5, skip = 2, digMode = 'd') + #Draw a star centered at origin, with cursor at a vertex. xOffset = x1 - x0 yOffset = y1 - y0 @@ -840,9 +1000,54 @@ def drawStar(x0, y0, z0, x1, y1, z1, n = 5, skip = 2, digMode = 'd') end end +def drawSpiral(x0, y0, z0, x1, y1, z1, coils, chord = 10, digMode = 'd') + # Draw a spiral centered at the origin, to a radius of the cursor. It makes turns, and if is >1, is made of points rather than a line.remaining + # Source: https://stackoverflow.com/questions/13894715/draw-equidistant-points-on-a-spiral + + # ('0'=no rotation, '1'=360 degrees, '180/360'=180 degrees) + rotation = 0 + + # value of theta corresponding to end of last coil + thetaMax = coils * 2 * Math::PI + + xOffset = x1 - x0 + yOffset = y1 - y0 + + radius = Math.sqrt(xOffset ** 2 + yOffset ** 2) + if (radius > 0.0) then + # How far to step away from center for each side. + awayStep = radius / thetaMax + + digAt(x0, y0, z0, digMode) + + # For every side, step around and away from center. + # start at the angle corresponding to a distance of chord + # away from centre. + theta = chord / awayStep + + while (theta.abs <= thetaMax.abs) + # How far away from center + away = awayStep * theta + + # How far around the center. + around = theta + rotation + + # Convert 'around' and 'away' to X and Y. + x = x0 + (Math.cos(around) * away).round + y = y0 + (Math.sin(around) * away).round + + digAt(x, y, z0, digMode) + # to a first approximation, the points are on a circle + # so the angle between them is chord/radius + theta += chord / away + end + end +end def floodfill(x,y,z,targetDig, digMode, maxCounter= 10000) + #Flood fills out from the cursor until different designation reached (eg if on 'd' fill only 'd', if on empty, fill only empty). Rooks move adjacency only. + #targetDig: what designation type can we overwrite? #digMode: what designation are we placing? #maxCounter: a limit to help with performance. @@ -852,7 +1057,7 @@ def floodfill(x,y,z,targetDig, digMode, maxCounter= 10000) if not t then #ignore impossible tiles (eg air.) - stdout "Tile does not exist" + stdout("Tile does not exist") throw :script_finished return end @@ -861,7 +1066,7 @@ def floodfill(x,y,z,targetDig, digMode, maxCounter= 10000) if t.designation.dig == digNum then #don't dig tiles that are already dug - stdout "Tile is already dug" + stdout("Tile is already dug") throw :script_finished return end @@ -902,8 +1107,8 @@ def floodfill(x,y,z,targetDig, digMode, maxCounter= 10000) counter = counter -1 if counter <=0 then - stdout " Max coverage of #{maxCounter} tiles reached. Use multiple floods, or add a number for max coverage as 'digshape flood [max coverage] [dig type]'." - stdout " Automatically cancelling flood" + stdout(" Max coverage of #{maxCounter} tiles reached. Use multiple floods, or add a number for max coverage as 'digshape flood [max coverage] [dig type]'.") + stdout(" Automatically cancelling flood") undo() return end @@ -928,55 +1133,17 @@ def floodfill(x,y,z,targetDig, digMode, maxCounter= 10000) end -# based on an algorithm in this stackoverflow question -# https://stackoverflow.com/questions/13894715/draw-equidistant-points-on-a-spiral -def drawSpiral(x0, y0, z0, x1, y1, z1, coils, chord = 10, digMode = 'd') - # ('0'=no rotation, '1'=360 degrees, '180/360'=180 degrees) - rotation = 0 - - # value of theta corresponding to end of last coil - thetaMax = coils * 2 * Math::PI - - xOffset = x1 - x0 - yOffset = y1 - y0 - - radius = Math.sqrt(xOffset ** 2 + yOffset ** 2) - if (radius > 0.0) then - # How far to step away from center for each side. - awayStep = radius / thetaMax - - digAt(x0, y0, z0, digMode) - - # For every side, step around and away from center. - # start at the angle corresponding to a distance of chord - # away from centre. - theta = chord / awayStep - - while (theta.abs <= thetaMax.abs) - # How far away from center - away = awayStep * theta - - # How far around the center. - around = theta + rotation - - # Convert 'around' and 'away' to X and Y. - x = x0 + (Math.cos(around) * away).round - y = y0 + (Math.sin(around) * away).round - digAt(x, y, z0, digMode) - # to a first approximation, the points are on a circle - # so the angle between them is chord/radius - theta += chord / away - end - end -end -# script execution start +=begin +======================== SCRIPT CONTROL +script execution start +=end if not $script_args[0] or $script_args[0]=="help" or $script_args[0]=="?" then - stdout " To draw downstair: digshape downstair depth" + stdout " To set origin: digshape origin" stdout " To draw line after origin is set: digshape line" stdout " To draw ellipse after origin is set (as bounding box): digshape ellipse " @@ -988,6 +1155,8 @@ def drawSpiral(x0, y0, z0, x1, y1, z1, coils, chord = 10, digMode = 'd') stdout " To draw a star after origin is set (as center) with the cursor as a vertex : digshape star <# points> " stdout "To draw an Archimedean spiral (coils - number of coils, chord - distance between points): digshape spiral " + stdout " To draw downstair: digshape downstair depth" + stdout " " stdout " To flood fill with a designation, overwriting ONLY the designation under the cursor (warning: slow on areas bigger than 10k tiles..): digshape flood [maxArea=10000]" stdout " To undo the previous command (restoring designation): digshape undo" stdout " To move all markers to the current z level (without displaying them): digshape resetz" @@ -999,7 +1168,7 @@ def drawSpiral(x0, y0, z0, x1, y1, z1, coils, chord = 10, digMode = 'd') $script_args.delete_at(0) if df.cursor.x == -30000 then - userSucks "Cursor must be on map" + userSucks("Cursor must be on map") end if not (command == 'undo' or command=='u') and not $isPreviewOnly then @@ -1007,6 +1176,7 @@ def drawSpiral(x0, y0, z0, x1, y1, z1, coils, chord = 10, digMode = 'd') end def requireOriginZLevel(msg: "Origin and target must be on the same z-level (use command 'digshape resetz' or 'digshape setz [Z-level, default=Cursor Z]' to fix)") + #Ensure cursor is on same z as origin (TODO: and control points). if df.cursor.z != $origin.z then userSucks(msg) end @@ -1014,6 +1184,7 @@ def requireOriginZLevel(msg: "Origin and target must be on the same z-level (use end def requireMajor(msg: "Set a point for the end of the major axis with the cursor and 'digshape major'") + #Ensure control point: 'major' has been set and is valid. if $major == nil then userSucks(msg) end @@ -1023,14 +1194,20 @@ def requireMajor(msg: "Set a point for the end of the major axis with the cursor end def getDigModeArgument(args) + #get next[LAST] script argument IFF it is a digmode designation, or set default if not present. argument = args[0] digMode = getDigMode(argument) + #if not ['d', 'u', 'j', 'i', 'h', 'r', 'x'].include? digMode then + # digMode='d' + #end args.delete_at(0) return digMode end -def getFilledArgument(args, default: false) # this doesn't *expect* and argument and so only consumes an argument when something matches +def getFilledArgument(args, default: false) + #get next script argument IFF it is fill. + # this doesn't *expect* and argument and so only consumes an argument when something matches argument = args[0] case argument when 'filled', 'f', 'true', 'yes', 'y'; filled = true @@ -1043,6 +1220,7 @@ def getFilledArgument(args, default: false) # this doesn't *expect* and argument end def getFloatArgument(args, default: nil, type: "(unnamed number)", positive: true) + #get next script argument, which must be a float. num = args[0] result = nil defaultMessage = "" @@ -1072,6 +1250,7 @@ def getFloatArgument(args, default: nil, type: "(unnamed number)", positive: tru end def getIntegerArgument(args, default: nil, type: "(unnamed integer)", positive: true) + #get next script argument, which must be an integer. num = args[0] result = nil defaultMessage = "" @@ -1134,16 +1313,25 @@ def noMoreArguments(args) end end + + + + +=begin +======================== DIGSHAPE COMMANDS +=end + + #def registerCommand(name, aliases, usage) # return "TODO:" #end + case command when 'origin', 'o', 'set' # $usage = createUsage(name: 'origin', aliases: ['o', 'set']) # TODO ADD USAGES TO EACH COMMANDS, make scriptError/userSucks print them out # Even better, to refator all these commands with associated data into classes / anonoymous functions so we can eventually do digshape help newOrigin = getPositionArgument($script_args, $origin, default: cursorAsDigPos()) - noMoreArguments($script_args) setOrigin(newOrigin.x, newOrigin.y, newOrigin.z) # need to refactor setOrigin @@ -1160,9 +1348,8 @@ def noMoreArguments(args) writeLuaPos("major", $major) - + stdout("Now move the cursor to the minor axis radius (extent) and call ellipse3p") - stdout "Now move the cursor to the minor axis radius (extent) and call ellipse3p" when 'resetz', 'setz' z = df.cursor.z # default @@ -1175,10 +1362,11 @@ def noMoreArguments(args) if $major then setMajor($major.x, $major.y, z) end + when 'ls', 'status' - stdout "origin: #{$origin != nil ? $origin.to_s : ''}" - stdout "major : #{$major != nil ? $major.to_s : ''}" - stdout "cursor: #{cursorAsDigPos().to_s}" + stdout("origin: #{$origin != nil ? $origin.to_s : ''}") + stdout("major : #{$major != nil ? $major.to_s : ''}") + stdout("cursor: #{cursorAsDigPos().to_s}") when 'line', 'l' digMode = getDigModeArgument($script_args) @@ -1215,7 +1403,7 @@ def noMoreArguments(args) requireMajor() if filled then - stdout "Filled not yet supported for 3p ellipses." + stdout("Filled not yet supported for 3p ellipses.") filled = false end @@ -1242,7 +1430,6 @@ def noMoreArguments(args) end digMode = getDigModeArgument($script_args) - noMoreArguments($script_args) requireOriginZLevel() @@ -1260,23 +1447,26 @@ def noMoreArguments(args) requireOriginZLevel() drawStar($origin.x, $origin.y, $origin.z, df.cursor.x, df.cursor.y, df.cursor.z, n, skip, digMode) + when 'spiral' - coils=getIntegerArgument($script_args, default: 2, type: "number of coils") - chord=getIntegerArgument($script_args, default: 1, type: "distance between points") - digMode = getDigModeArgument($script_args) - noMoreArguments($script_args) + coils=getIntegerArgument($script_args, default: 2, type: "number of coils") + chord=getIntegerArgument($script_args, default: 1, type: "distance between points") + digMode = getDigModeArgument($script_args) + noMoreArguments($script_args) + + drawSpiral($origin.x, $origin.y, $origin.z, df.cursor.x, df.cursor.y, df.cursor.x, coils, chord, digMode) - drawSpiral($origin.x, $origin.y, $origin.z, df.cursor.x, df.cursor.y, df.cursor.x, coils, chord, digMode) when 'keupo', 'stairs', 'downstairs', 'downstair' #digshape keupo depth depth = getIntegerArgument($script_args, type: "depth") noMoreArguments($script_args) if depth <= 0 then - userSucks "Depth must be an integer greater than zero" + userSucks("Depth must be an integer greater than zero") end digKeupoStair(df.cursor.x, df.cursor.y, df.cursor.z, depth) + when 'flood', 'f' maxArea = getIntegerArgument($script_args, default: 10000, type: "maximum flood area") digMode = getDigModeArgument($script_args) @@ -1288,12 +1478,15 @@ def noMoreArguments(args) if targetDig != :No then if targetDig == digMode2enum(digMode) then - userSucks "Floodfill must be centered on an undesignated/matching tile." + userSucks("Floodfill must be centered on an undesignated/matching tile.") end end - floodfill(df.cursor.x, df.cursor.y, df.cursor.z, targetDig, dig, maxArea) - when 'undo' - unDig() + + floodfill(df.cursor.x, df.cursor.y, df.cursor.z, targetDig, digMode, maxArea) + + when 'undo', 'u' + undo() + else - userSucks "Invalid command" + userSucks("Invalid command") end \ No newline at end of file From ad6c0a8c379f278d65753e85f1c6879e469246b2 Mon Sep 17 00:00:00 2001 From: Quatch Date: Fri, 26 Mar 2021 02:11:15 -0400 Subject: [PATCH 05/12] Added 'swap' command added swap to digshape, and gui. added better pen options for gui. added a bit of chrome to menu. --- digshape.rb | 20 ++++++++++ gui/digshape.lua | 95 ++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 96 insertions(+), 19 deletions(-) diff --git a/digshape.rb b/digshape.rb index 3a2424f..52b7af0 100644 --- a/digshape.rb +++ b/digshape.rb @@ -49,6 +49,9 @@ To move all of the markers to the current z level: digshape resetz + To swap the origin and cursor: + digshape swap + All commands accept a digging designation mode as a single character argument [dujihrx], otherwise will default to 'd' =end @@ -1161,6 +1164,7 @@ def floodfill(x,y,z,targetDig, digMode, maxCounter= 10000) stdout " To flood fill with a designation, overwriting ONLY the designation under the cursor (warning: slow on areas bigger than 10k tiles..): digshape flood [maxArea=10000]" stdout " To undo the previous command (restoring designation): digshape undo" stdout " To move all markers to the current z level (without displaying them): digshape resetz" + stdout " To swap the origin and cursor markers: digshape swap" stdout " All commands accept a one letter digging designation [dujihrx] at the end, or will default to 'd'" throw :script_finished end @@ -1363,6 +1367,22 @@ def noMoreArguments(args) if $major then setMajor($major.x, $major.y, z) end + when 'swap' + stdout("origin: #{$origin != nil ? $origin.to_s : ''}") + stdout("major : #{$major != nil ? $major.to_s : ''}") + stdout("cursor: #{cursorAsDigPos().to_s}") + stdout("---swap---") + + temp=$origin + $origin=cursorAsDigPos() + df.cursor.x=temp.x + df.cursor.y=temp.y + df.cursor.z=temp.z + + stdout("origin: #{$origin != nil ? $origin.to_s : ''}") + stdout("major : #{$major != nil ? $major.to_s : ''}") + stdout("cursor: #{cursorAsDigPos().to_s}") + when 'ls', 'status' stdout("origin: #{$origin != nil ? $origin.to_s : ''}") diff --git a/gui/digshape.lua b/gui/digshape.lua index ce07a7b..44c5559 100644 --- a/gui/digshape.lua +++ b/gui/digshape.lua @@ -21,8 +21,33 @@ DigshapeUI.ATTRS { currentCommand = 'circle hollow @', currentOutput = {}, currentError = {}, - currentDig = {} + currentDig = {}, -- default properties for self here + + --Preview mode variables + blink = false, --should the preview blink? + blinkrate = { 3, 750, 350, 125 }, --how fast do we blink. blinkrate[1] is the index of the chosen rate. + pens={ + --{key, symbol character code, fgcolor, bgcolor} + ['origin']=dfhack.pen.make({ch='+',fg=COLOR_CYAN,bg=COLOR_LIGHTGREEN}), + ['cursor']=dfhack.pen.make({ch='X',fg=COLOR_CYAN,bg=COLOR_LIGHTGREEN}), + ['major']=dfhack.pen.make({ch='a',fg=COLOR_CYAN,bg=COLOR_LIGHTGREEN}), + ['designation']=dfhack.pen.make({fg=COLOR_BROWN,bg=COLOR_YELLOW}), + ['mark']=dfhack.pen.make({fg=COLOR_YELLOW,bg=COLOR_LIGHTCYAN}), + ['clear']=dfhack.pen.make({fg=COLOR_BROWN,bg=COLOR_RED}) + }, + + --Designation mode variables + designateMarking = false, --are we designating "marking" rather than standard? + + + --Mouse variables + mouse = true, + mousebuttons={'commit','origin','major'}, + dragging = false, + lastMouse = xyz2pos(0, 0, 0), + lerp = true, --lerp is laggy because it repeatedly inefficiently accesses memory; it would be faster if we cached some but thats above my paygrade ;) + customOffset = { x = 0, y = 0 }, } @@ -34,29 +59,44 @@ local digButtons={ {key='j', symbol=">", text="Down Stair"}, {key='u', symbol="<", text="Up Stair"}, {key='x', symbol=" ", text="Remove Designation"}, + {key='M', symbol=" ", text="Toggle Dig/Mark"} } local digModeToButton = {} for _, data in pairs(digButtons) do - data.keybind = ("CUSTOM_%s"):format(data.key:upper()) + data.keybind = ("CUSTOM_".. (string.match(data.key,"%u") ~=nil and "SHIFT_" or "") .."%s"):format(data.key:upper()) digModeToButton[data.key] = data + print("~") end local buttons = { - {key="p", text="Set digshape command", callback=function(self) + --key: the key that will be bound to callback. For non-button entries (eg blank lines), set key="NOTKEY" to skip the keybinding. + --text: the label for the keybind + --prelabel: a menu rendering function exicuted before displaying the keybinding, use to newline or set color, etc + --postlabel: as prelabel, but after the keybinding label is printed. + --callback: the function called by this button + {key="p", text="Set digshape command", postlabel=function(self, dc) + dc:newline(1):advance(3):pen(COLOR_YELLOW):string("[ "..self.currentCommand.." ]") + end, callback=function(self) dialog.showInputPrompt("Set digshape command", "Enter a digshape command", COLOR_WHITE, self.currentCommand, function(result) self.currentCommand=result self:runCurrentCommand(true) end) end}, - {key="o", text="Set origin", callback=function(self) + {key="o", text="Set origin",prelabel=function(self,dc)dc:newline(1) if self.origin ~=nil then dc:pen(COLOR_LIGHTGREEN) else dc:pen(COLOR_LIGHTRED) end end, callback=function(self) dfhack.run_command_silent("digshape lua origin") self:runCurrentCommand(true) end}, - {key="m", text="Set major", callback=function(self) + {key="s", text="Swap origin/cursor", prelabel=function(self,dc)dc:newline(1):advance(2) if self.origin ~=nil then dc:pen(COLOR_LIGHTGREEN) else dc:pen(COLOR_LIGHTRED) end end, callback=function(self) + dfhack.run_command_silent("digshape lua swap") + self:runCurrentCommand(true) + end}, + + {key="a", text="Set major", callback=function(self) dfhack.run_command_silent("digshape lua major") self:runCurrentCommand(true) end}, + {key="NOTKEY",prelabel=function(self,dc)dc:newline(2) end}, {key="SELECT", keybind="SELECT", text="Execute command", callback=function(self) self:runCurrentCommand(false) end}, @@ -74,14 +114,14 @@ local lastZ = df.global.cursor.z function DigshapeUI:runCurrentCommand(preview) local command = ("digshape lua %s%s"):format(preview and "preview " or "", self.currentCommand):gsub("@", self.activeDesgination) - --print(("command='%s'"):format(command)) + print(("command='%s'"):format(command)) local output = dfhack.run_command_silent(command) self.currentOutput = {} self.currentError = {} self.currentDig = {} self.origin = nil self.major = nil - --print("output=", output) + print("output=", output) for line in output:gmatch("[^\r\n]+") do messageType = line:match("^([^:]+):") if messageType == "msg" then @@ -140,14 +180,14 @@ function DigshapeUI:renderOverlay() end for _, dig in ipairs(self.currentDig) do - paintMapTile(dc, vp, df.global.cursor, xyz2pos(dig.x, dig.y, dig.z), dig.symbol, COLOR_BLACK, self.activeDesgination=='x' and COLOR_RED or COLOR_BROWN) + paintMapTile(dc, vp, df.global.cursor, xyz2pos(dig.x, dig.y, dig.z), dig.symbol, self.activeDesgination=='x' and self.pens['clear'] or self.pens['designation']) end if self.origin then - paintMapTile(dc, vp, df.global.cursor, self.origin, '+', COLOR_YELLOW) + paintMapTile(dc, vp, df.global.cursor, self.origin, '+', self.pens['origin']) end if self.major then - paintMapTile(dc, vp, df.global.cursor, self.major, '+', COLOR_LIGHTGREEN) + paintMapTile(dc, vp, df.global.cursor, self.major, '+', self.pens['major']) end @@ -156,21 +196,38 @@ end function DigshapeUI:onRenderBody(dc) self:renderOverlay() - dc:clear():seek(1,1):pen(COLOR_WHITE):string("Digshape - Main menu") + dc:clear():seek(1, 1):pen(COLOR_WHITE):string("Digshape - " .. self.state--:gsub("^%a", function(x) + -- return x:upper() + -- end) + ) dc:seek(1,3) + if true or self.state=="preview" then for _, data in pairs(digButtons) do - builder = dc:key_string(data.keybind, data.text, self.activeDesgination==data.key and COLOR_WHITE or COLOR_GREY):newline(1) - if data.key=='x' then - builder:newline(1) - end + builder = dc:newline(1):key_string(data.keybind, data.text, self.activeDesgination==data.key and COLOR_WHITE or COLOR_GREY) + -- if data.key=='x' then + -- builder:newline(1) + -- end end + + dc:newline(2) + for _, data in pairs(buttons) do - builder = dc:key_string(data.keybind, data.text, COLOR_GREY):newline(1) - if data.key=='m' then - builder:newline(1) + if data.prelabel ~= nil then + data.prelabel(self,dc) + else + dc:newline(1):pen(COLOR_GREY) + end + if data.key~="NOTKEY" then + builder = dc:key_string(data.keybind, data.text) + end + if data.postlabel ~= nil then + data.postlabel(self,dc) + end + --if data.key=='m' then + --builder:newline(1) + --end end - end --[[ dc:key_string("CUSTOM_S", "Set Brush",COLOR_GREY) From 742b064e17dfcdb544afcc9e728e8ada7faca52b Mon Sep 17 00:00:00 2001 From: Quatch Date: Fri, 26 Mar 2021 02:24:43 -0400 Subject: [PATCH 06/12] Update README.md --- README.md | 42 ++++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 79df5df..391d149 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ You can add a digging designation to the end of any of the drawing commands - [dujihrx]. The default is 'd'. -For example, `digshape star 5 2 j` will dig a 5 pointed star out of downstairs with the mark as the center and the cursor as a vertex on the star +For example, `digshape star 5 2 j` will dig a 5 pointed star out of 'downstair' designations with the mark as the center and the cursor as a vertex on the star. ## In-game help digshape ? @@ -12,15 +12,15 @@ For example, `digshape star 5 2 j` will dig a 5 pointed star out of downstairs w *The origin is used for most drawing operations. It persists between operations. It is Step 1 in any of the following operations.* -## draw a line +## Draw a line digshape line *draws a straight line from cursor to mark* -## draw an ellipse +## Draw an ellipse digshape ellipse [filled] *Step 2: draw an ellipse contained within the bounding box formed of the mark and current cursor.* -### draw an ellipse using 3 pt +### Draw an ellipse using 3 pt digshape major *Step 2: set the end of the major axis from mark to cursor.* @@ -28,11 +28,11 @@ For example, `digshape star 5 2 j` will dig a 5 pointed star out of downstairs w *Step 3: draw the ellipse using new cursor as the semiminor axis length (midpoint of major axis->cursor)* *Note: ellipse3p cannot yet be filled by an argument, use flood* -## draw a circle with arbitrary diameter +## Draw a circle with arbitrary diameter digshape circle2p *Step 2: mark and cursor form the diameter of the circle (at any tilt).* -## draw a 3 pt bezier curve +## Draw a 3 pt bezier curve digshape major *Step 2: set the endpoint of the curve* @@ -44,47 +44,49 @@ Step 1: place start of curve at cursor: `digshape origin`\ Step 2: move cursor to end of curve, then: `digshape major`\ Step 3: move cursor to the side of the line to be the control point for the curve, then: `digshape bez`, or `digshape bez 9` for a curve that gets closer to the cursor. -## draw a polygon with cursor as vertex +## Draw a polygon with cursor as vertex digshape polygon *Step 2: draw a polygon with sides using the mark as center and cursor as a vertex* _eg:_ `digshape polygon 5 h`: draws a pentagram of channel designations, with the mark as the center, and the cursor as one of the verticies. \ _eg:_ `digshape polygon 6`: draws a hexagon of dig designations, with the mark as the center, and the cursor as one of the verticies. -## draw a polygon with cursor as apothem +## Draw a polygon with cursor as apothem digshape polygon apothem *Step 2: Draw a polygon with n sides, with the mark as center, and cursor as a midpoint of one of the sides [apothem, like a radius]* ## Draw a star polygon In [Schläfli symbol notation](https://en.wikipedia.org/wiki/Schl%C3%A4fli_symbol) -*mark as center and cursor as a vertex* +*Origin as center and cursor as a vertex* + digshape star [skip=2] -## draw a point with n-fold symmetry +## Draw a point with n-fold symmetry digshape star *Step 2: draw a point at the cursor, and at n points around the origin at the same radius, as though they were verticies of a star without drawing the connecting lines. (eg for 5fold: "digshape star 5 5"). It is helpful to bind this to a keycombo, so that it can be used to draw.* -## Draw an Archimedean spiral with specified number of "coils", each point separated by "chord" tiles +## Draw an Archimedean spiral digshape spiral +*Draws a spiral with specified number of "coils", each point separated by "chord" tiles* -## flood fill an area +## Flood fill an area digshape flood [max coverage=10000] *Fill an area with a dig designation. Will only fill tiles that match the designation under the cursor. Ignores/does not require the origin to be set.* *Note: Larger max coverages can take time to fill. A great way to fill in the above shapes.* -## undo last digshape command +## Undo last digshape command digshape undo -*restores designations for the last digshape command. Will not record manual or other commands designations, but won't loose it's record.* +*Restores designations for the last digshape command. Will not record manual or other commands designations, but won't loose it's record.* -## move all markers to the current z level +## Move all markers to the current z level digshape resetz -*moves the markers (origin, major) to the current z level* - - digshape star [skip=2] +*Moves the markers (origin, major) to the current z level* +## Swap origin and cursor markers + digshape swap +*Swaps the cursor and the origin marker, handy for walking a spline* + -## Draw an Archimedean spiral with specified number of "coils", each point separated by "chord" tiles - digshape spiral # Contributors From cc06e12273f811890b8394b530de59c200f719d4 Mon Sep 17 00:00:00 2001 From: Quatch Date: Mon, 29 Mar 2021 22:27:28 -0400 Subject: [PATCH 07/12] Mostly functional GUI Switched to Widgets.Label to drive buttons. Revised what is shown and how. --- .gitignore | 1 + digshape.rb | 141 +++++-- gui/digshape.lua | 1009 ++++++++++++++++++++++++++++++++++++++-------- 3 files changed, 953 insertions(+), 198 deletions(-) diff --git a/.gitignore b/.gitignore index 5e1422c..4cc98f5 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ /test/tmp/ /test/version_tmp/ /tmp/ +/.idea/ # Used by dotenv library to load environment variables. # .env diff --git a/digshape.rb b/digshape.rb index 52b7af0..e533cc6 100644 --- a/digshape.rb +++ b/digshape.rb @@ -1,4 +1,5 @@ -# dig shapes +# designate geometric shapes + =begin digshape @@ -8,7 +9,7 @@ Commands that do not require a set origin: To dig a 3x3 sparse up-down stairway: - digshape downstair + digshape downstair (differentStart? [default: true]) To undo the previous command (restoring designation): digshape undo @@ -131,12 +132,23 @@ $isPreviewOnly = false if $isLuaMode then +# puts "-->> Scriptargs:" +# puts $script_args +# puts "<<--" + $script_args.delete_at(0) $isPreviewOnly = $script_args[0] == "preview" if $isPreviewOnly then $script_args.delete_at(0) + if $script_args[0] == "argreference" then + puts "ref:ARGREFTABLE" + $script_args.delete_at(0) + #throw :script_finished + end end + $output + end def writeLuaPos(name, digPos) # pos:::: @@ -928,9 +940,15 @@ def drawEllipse(x0, y0, z0, x1, y1, z1, x2=nil, y2=nil, z2=nil, filled = false, end end -def digKeupoStair(x, y, z, depth) +def digKeupoStair(x, y, z, depth,differentStart=true) #Dig an X of updown stairs (corners and center of a 3x3) centered on cursor, down a number of zlevels. iz = z + startingDesignation='' + if (differentStart) then + startingDesignation='j' + else + startingDesignation='i' + end digAt(x, y, iz, 'j') digAt(x - 1, y + 1, iz, 'j') digAt(x - 1, y - 1, iz, 'j') @@ -1003,12 +1021,13 @@ def drawStar(x0, y0, z0, x1, y1, z1, n = 5, skip = 2, digMode = 'd') end end -def drawSpiral(x0, y0, z0, x1, y1, z1, coils, chord = 10, digMode = 'd') +def drawSpiral(x0, y0, z0, x1, y1, z1, coils, chord = 10,rotation=0, digMode = 'd') # Draw a spiral centered at the origin, to a radius of the cursor. It makes turns, and if is >1, is made of points rather than a line.remaining # Source: https://stackoverflow.com/questions/13894715/draw-equidistant-points-on-a-spiral - # ('0'=no rotation, '1'=360 degrees, '180/360'=180 degrees) - rotation = 0 + + + # value of theta corresponding to end of last coil thetaMax = coils * 2 * Math::PI @@ -1016,6 +1035,11 @@ def drawSpiral(x0, y0, z0, x1, y1, z1, coils, chord = 10, digMode = 'd') xOffset = x1 - x0 yOffset = y1 - y0 + # ('0'=no rotation, '1'=360 degrees, '180/360'=180 degrees, <0=track cursor) + rotation=Math.atan2(yOffset,xOffset)+rotation*0.01745 #convert to radians + + + radius = Math.sqrt(xOffset ** 2 + yOffset ** 2) if (radius > 0.0) then # How far to step away from center for each side. @@ -1157,9 +1181,8 @@ def floodfill(x,y,z,targetDig, digMode, maxCounter= 10000) stdout " To draw a polygon after origin is set (as center) with the cursor as a vertex: digshape polygon <# sides>" stdout " To draw a polygon after origin is set (as center) with the cursor as a midpoint of a segment(apothem): digshape polygon <# sides> apothem" stdout " To draw a star after origin is set (as center) with the cursor as a vertex : digshape star <# points> " - stdout "To draw an Archimedean spiral (coils - number of coils, chord - distance between points): - digshape spiral " - stdout " To draw downstair: digshape downstair depth" + stdout "To draw an Archimedean spiral (coils - number of coils, chord - distance between points): digshape spiral " + stdout " To draw downstair: digshape downstair depth [differentStart?=true]" stdout " " stdout " To flood fill with a designation, overwriting ONLY the designation under the cursor (warning: slow on areas bigger than 10k tiles..): digshape flood [maxArea=10000]" stdout " To undo the previous command (restoring designation): digshape undo" @@ -1185,7 +1208,7 @@ def requireOriginZLevel(msg: "Origin and target must be on the same z-level (use if df.cursor.z != $origin.z then userSucks(msg) end - writeLuaPos("origin",$origin) # visualize them for the user + #writeLuaPos("origin",$origin) # visualize them for the user end def requireMajor(msg: "Set a point for the end of the major axis with the cursor and 'digshape major'") @@ -1194,30 +1217,33 @@ def requireMajor(msg: "Set a point for the end of the major axis with the cursor userSucks(msg) end requireOriginZLevel() - writeLuaPos("origin", $origin) # visualize them for the user - writeLuaPos("major", $major) # visualize them for the user + #writeLuaPos("origin", $origin) # visualize them for the user + #writeLuaPos("major", $major) # visualize them for the user end def getDigModeArgument(args) #get next[LAST] script argument IFF it is a digmode designation, or set default if not present. argument = args[0] digMode = getDigMode(argument) - #if not ['d', 'u', 'j', 'i', 'h', 'r', 'x'].include? digMode then - # digMode='d' - #end - args.delete_at(0) - + if not ['d', 'u', 'j', 'i', 'h', 'r', 'x'].include? digMode then + digMode='d' + else + args.delete_at(0) + end return digMode end + def getFilledArgument(args, default: false) #get next script argument IFF it is fill. # this doesn't *expect* and argument and so only consumes an argument when something matches argument = args[0] + case argument when 'filled', 'f', 'true', 'yes', 'y'; filled = true when 'hollow', 'h', 'false', 'no', 'n'; filled = false else + puts( "DEFAULT?>@#$#@") return default # doesn't consume if nothing matches end args.delete_at(0); @@ -1284,6 +1310,34 @@ def getIntegerArgument(args, default: nil, type: "(unnamed integer)", positive: return result end +def getBooleanArgument(args, default: nil, type: "(unnamed integer)") + #get next script argument, which must be an integer. + num = args[0] + result = nil + defaultMessage = "" + + if default != nil then + defaultMessage = "Use `-' for the default value (#{default})" + end + + if not num then + userSucks("Must supply #{type} parameter (boolean).#{defaultMessage}") + end + args.delete_at(0) + + case num + when 'default','-'; + userSucks("No default value for #{type} parameter!") if default == nil + result = default + else + result = num==true rescue userSucks("Malformed boolean for "+type+" parameter, got `"+num+"'.#{defaultMessage}") + end + + + + return result +end + def makeDefaultPosMap(oldPos) return { '~' => cursorAsDigPos(), # cursor positon @@ -1341,9 +1395,9 @@ def noMoreArguments(args) setOrigin(newOrigin.x, newOrigin.y, newOrigin.z) # need to refactor setOrigin - writeLuaPos("origin", $origin) + #writeLuaPos("origin", $origin) - when 'major', 'm' #used to mark the end point of the major diameter + when 'major', 'm', 'a' #used to mark the end point of the major diameter newMajor = getPositionArgument($script_args, $major, default: cursorAsDigPos()) noMoreArguments($script_args) @@ -1351,17 +1405,17 @@ def noMoreArguments(args) setMajor(newMajor.x, newMajor.y, newMajor.z) # need to refactor setMajor - writeLuaPos("major", $major) + #writeLuaPos("major", $major) stdout("Now move the cursor to the minor axis radius (extent) and call ellipse3p") when 'resetz', 'setz' z = df.cursor.z # default - if args[0] then - z = getPosComponentArgument(args, makeDefaultPosMap(origin), :z) # only really need the z-component from origin for default - end - noMoreArguments($script_args) +# if args[0] then +# z = getPosComponentArgument(args, makeDefaultPosMap(origin), :z) # only really need the z-component from origin for default +# end +# noMoreArguments($script_args) setOrigin($origin.x, $origin.y, z) if $major then @@ -1385,9 +1439,18 @@ def noMoreArguments(args) when 'ls', 'status' - stdout("origin: #{$origin != nil ? $origin.to_s : ''}") - stdout("major : #{$major != nil ? $major.to_s : ''}") - stdout("cursor: #{cursorAsDigPos().to_s}") + if $isLuaMode then + writeLuaPos("origin", $origin) + writeLuaPos("major", $major) + if $digBufferX.length >0 then + puts "Undo Buffer exists" + end + throw :script_finished + else + stdout("origin: #{$origin != nil ? $origin.to_s : ''}") + stdout("major : #{$major != nil ? $major.to_s : ''}") + stdout("cursor: #{cursorAsDigPos().to_s}") + end when 'line', 'l' digMode = getDigModeArgument($script_args) @@ -1398,17 +1461,17 @@ def noMoreArguments(args) drawLine($origin.x, $origin.y, $origin.z, df.cursor.x, df.cursor.y, df.cursor.z, digMode) when 'ellipse', 'e' #digshape ellipse [filled] [digmode] - filled = getFilledArgument($scripts_args) + + filled = getFilledArgument($script_args) digMode = getDigModeArgument($script_args) noMoreArguments($script_args) - requireOriginZLevel() drawEllipse($origin.x, $origin.y, $origin.z, df.cursor.x, df.cursor.y, df.cursor.z, x2=nil, y2=nil, z2=nil, filled=filled, digMode=digMode, mode='bbox') # fixme: default arguments should be colon not equals when 'circle2p', 'circle', 'c' #digshape circle2p [filled] [digmode] filled = getFilledArgument($script_args) - digMode = getDigModeArgument($script_args) #check argument 1 for dig instructions + digMode = getDigModeArgument($script_args) noMoreArguments($script_args) requireOriginZLevel() @@ -1430,7 +1493,7 @@ def noMoreArguments(args) drawEllipse($origin.x, $origin.y, $origin.z, $major.x, $major.y, $major.z, df.cursor.x, df.cursor.y, df.cursor.z, filled=filled, digMode=digMode, mode='axis') # fixme: default arguments should be colon not equals - when 'bezier', 'bez', 'b' #digshape bezier [weight] digmode] + when 'bezier', 'bez', 'b', 'curve' #digshape bezier [weight] digmode] #use origin and major as endpoints, cursor as curve shaper weight = getFloatArgument($script_args, default: 1.5, type: "bezier weight") digMode = getDigModeArgument($script_args) #check argument 1 for dig instructions @@ -1459,10 +1522,8 @@ def noMoreArguments(args) when 'star', 's' #digshape star N [skip=2] [digMode] n = getIntegerArgument($script_args, type: "star n-sides") - skip = getIntegerArgument($script_args, default: 2, type: "skip") digMode = getDigModeArgument($script_args) - noMoreArguments($script_args) requireOriginZLevel() @@ -1472,14 +1533,17 @@ def noMoreArguments(args) when 'spiral' coils=getIntegerArgument($script_args, default: 2, type: "number of coils") chord=getIntegerArgument($script_args, default: 1, type: "distance between points") + rotate = getIntegerArgument($script_args, default: 0, type: "rotate", positive: false) digMode = getDigModeArgument($script_args) noMoreArguments($script_args) - drawSpiral($origin.x, $origin.y, $origin.z, df.cursor.x, df.cursor.y, df.cursor.x, coils, chord, digMode) + requireOriginZLevel() + + drawSpiral($origin.x, $origin.y, $origin.z, df.cursor.x, df.cursor.y, df.cursor.x, coils, chord, rotate, digMode) when 'keupo', 'stairs', 'downstairs', 'downstair' #digshape keupo depth depth = getIntegerArgument($script_args, type: "depth") - + start = getBooleanArgument($script_args, type: "different Start?", default: true) noMoreArguments($script_args) if depth <= 0 then @@ -1510,4 +1574,9 @@ def noMoreArguments(args) else userSucks("Invalid command") -end \ No newline at end of file +end + +#Report back to lua our status. +writeLuaPos("origin", $origin) +writeLuaPos("major", $major) +writeLuaPos("cursor", cursorAsDigPos()) #lua doesn't need this, but it is handy for dev for it to be printed at the same time. \ No newline at end of file diff --git a/gui/digshape.lua b/gui/digshape.lua index 44c5559..f597fc2 100644 --- a/gui/digshape.lua +++ b/gui/digshape.lua @@ -1,138 +1,619 @@ ---designating tool - +--gui front-end for digshape.rb, a geometric designations generating tool --[====[ gui/digshape =========== -gui front-end for digshape.rb - +gui front-end for digshape.rb, a geometric designations generating tool ]====] +verbose = false --TODO: move these down later to be arguments +--dfhack.screen.invalidate() --force an immediate redraw. + +--[[ +=======Useful dfhack tidbits======== +"kill-lua": kills running lua scripts, opt: "force" +"devel/pop-screen": exit an active gui script +"devel/clear-script-env SCRIPTNAME" +"lua onelinescript........": run a lua command directly. + ----"lua _G.digshape-gui_saved_options=nil": clears digshapegui's special save +"devel/click-monitor start|stop" : prints coordinates of mouse clicks to console + +=====Console color constants +COLOR_RESET = -1 +COLOR_BLACK = 0 +COLOR_BLUE = 1 +COLOR_GREEN = 2 +COLOR_CYAN = 3 +COLOR_RED = 4 +COLOR_MAGENTA = 5 +COLOR_BROWN = 6 +COLOR_GREY = 7 +COLOR_DARKGREY = 8 +COLOR_LIGHTBLUE = 9 +COLOR_LIGHTGREEN = 10 +COLOR_LIGHTCYAN = 11 +COLOR_LIGHTRED = 12 +COLOR_LIGHTMAGENTA = 13 +COLOR_YELLOW = 14 +COLOR_WHITE = 15 + +====GUI stuff +Lua api: https://docs.dfhack.org/en/stable/docs/Lua%20API.html# +1st half of GUI module: https://docs.dfhack.org/en/stable/docs/Lua%20API.html#gui-module +2nd half of GUI module: https://docs.dfhack.org/en/stable/docs/Lua%20API.html#screen-api +3rd half of GUI module: https://docs.dfhack.org/en/stable/docs/Lua%20API.html#in-game-ui-library +Painter: https://docs.dfhack.org/en/stable/docs/Lua%20API.html#painter-class +Widgets: https://docs.dfhack.org/en/stable/docs/Lua%20API.html#gui-widgets +See also: dfhack.lua, class.lua, dwarfmode.lua, gui.lua, widgets.lua, dialogs.lua as the html helpfiles do not adaquately describe the functionality. + +====Label widget: +Label.ATTRS{ + text_pen = COLOR_WHITE, + text_dpen = COLOR_DARKGREY, -- disabled + text_hpen = DEFAULT_NIL, -- highlight - default is text_pen with reversed brightness + disabled = DEFAULT_NIL, + enabled = DEFAULT_NIL, + auto_height = true, + auto_width = false, + on_click = DEFAULT_NIL, + on_rclick = DEFAULT_NIL, +} +--]] + local utils = require "utils" local gui = require "gui" local guidm = require "gui.dwarfmode" local dialog = require "gui.dialogs" +local widgets = require 'gui.widgets' + +stdout = function(...) +end--silently discard + +if verbose then + --atm verbose must be set manually, see line2 of this script. + dfhack.console.clear() + stdout = function(msgtype, ...) + --A stupid pretty console print command. + local prefix = "" + if type(msgtype) == "string" then + _, _, temp = string.find(msgtype, "^(...)$") + if temp == "ERR" or temp == "MSG" or temp == "WRN" or temp == "OUT" or temp == "CMD" or temp == "RBY" then + --output channels: MSG/WRN/ERR: information, OUT: results, RBY: digshape output passed through. + if temp == "IGNORE" or temp == "ALSOIGNORE" or temp == "RBlY" then + return --don't print + end + + if verbose == false and temp ~= "ERR" then + return --only print errors when not verbose + end + + prefix = " DS: " .. msgtype .. ": " + msgtype = "" + else + prefix = " DS: " + end + + end + print(prefix, msgtype, ...) + end + --TOOD: fancy! https://www.lua.org/pil/6.html +else + -- print=nil --suckers, don't print anything. +end + +--stdout("gui/Digshape verbose output"," ".."!") +--stdout("ERR", "gui/Digshape verbose output"," ".."!") +--stdout("ERR", "A", "B", "C","D".."D") +--stdout("A", "B", "C","D".."D") DigshapeUI = defclass(DigshapeUI, guidm.MenuOverlay) DigshapeUI.ATTRS { state = "preview", activeDesgination = 'd', - currentCommand = 'circle hollow @', + currentCommand = 'spiral', --TODO: replace this with self.digshapeCommands.current + parsedCommand = DEFAULT_NIL, --TODO: move this to self.digshapeCommands.current.parsedCommand + currentOutput = {}, currentError = {}, currentDig = {}, + origin = nil, + major = nil, + autosetZtoCurrent = true, -- default properties for self here --Preview mode variables blink = false, --should the preview blink? blinkrate = { 3, 750, 350, 125 }, --how fast do we blink. blinkrate[1] is the index of the chosen rate. - pens={ + pens = { --{key, symbol character code, fgcolor, bgcolor} - ['origin']=dfhack.pen.make({ch='+',fg=COLOR_CYAN,bg=COLOR_LIGHTGREEN}), - ['cursor']=dfhack.pen.make({ch='X',fg=COLOR_CYAN,bg=COLOR_LIGHTGREEN}), - ['major']=dfhack.pen.make({ch='a',fg=COLOR_CYAN,bg=COLOR_LIGHTGREEN}), - ['designation']=dfhack.pen.make({fg=COLOR_BROWN,bg=COLOR_YELLOW}), - ['mark']=dfhack.pen.make({fg=COLOR_YELLOW,bg=COLOR_LIGHTCYAN}), - ['clear']=dfhack.pen.make({fg=COLOR_BROWN,bg=COLOR_RED}) + --https://docs.dfhack.org/en/stable/docs/Lua%20API.html#pen-api + + origin = dfhack.pen.make({ ch = '+', fg = COLOR_CYAN, bg = COLOR_LIGHTGREEN }), + cursor = dfhack.pen.make({ ch = 'X', fg = COLOR_CYAN, bg = COLOR_BLACK }), + ctrl_A = dfhack.pen.make({ ch = 'a', fg = COLOR_CYAN, bg = COLOR_LIGHTCYAN }), + designation = dfhack.pen.make({ fg = COLOR_BROWN, bg = COLOR_YELLOW }), + mark = dfhack.pen.make({ fg = COLOR_YELLOW, bg = COLOR_LIGHTCYAN }), + clear = dfhack.pen.make({ fg = COLOR_BROWN, bg = COLOR_RED }), + digMode = { + selected = dfhack.pen.make({ fg = COLOR_BLACK, bg = COLOR_LIGHTGREEN }), + deselected = dfhack.pen.make({ fg = COLOR_LIGHTGREEN, bg = COLOR_BLACK }), + delete = dfhack.pen.make({ fg = COLOR_BLACK, bg = COLOR_LIGHTRED }), + mark = dfhack.pen.make({ fg = COLOR_BLACK, bg = COLOR_LIGHTCYAN }), + }, }, - --Designation mode variables + --Designation mode variables (to be saved between commands) + -- designateDigMode = {}, --what is the UI selected digMode? designateMarking = false, --are we designating "marking" rather than standard? + designateFilled = false, --filled or hollow? (will be ignored if current command does not allow/use it) + + + + digButtons = { + --, keypen={selected=self.pens.digMode.,deselected=self.pens.digMode.,}}, + d = { key = 'd', symbol = " ", text = "Mine", }, + i = { key = 'i', symbol = "X", text = "U/D Stair" }, + h = { key = 'h', symbol = "_", text = "Channel" }, + r = { key = 'r', symbol = 30, text = "Up Ramp" }, + j = { key = 'j', symbol = ">", text = "Down Stair" }, + u = { key = 'u', symbol = "<", text = "Up Stair" }, + x = { key = 'x', symbol = " ", text = "Remove Designation" }, + M = { key = 'M', symbol = " ", text = "Toggle Dig/Mark" }, + order = "_dihrjuxM_", --we need to know the order so we can insert/remove cursor markers in the designation list. It's a hack, sorry. + }, + + + --Digshape command reference + digshapeCommands = { + --[[ + --This table records the names, requiremens, and arguments for each digshape command. + --requireOrigin[bool]: do we need the origin to be set? + --requireMajor[bool]: " controlPointA (major) set? + --requireZ[bool]: " cursor/view on same z as origin/controlPoints? + --allowFilled[bool]: Can this command accept filled/hollow? (eg. "line" has no volume and so cannot be either). + --runSilent[bool]: should this command always be run silent? --TODO: I don't use this yet, and don't quite remember why I thought we needed it. + --digMode [string OR nil]: what the digMode is allowed to be. "@": any, "nil": none, "[character]": this/these and only these. + --desc: Short description of this command, used as in-game help. + --args [{{}{}...{}} OR nil]: details on the arguments to this command. If no args accepted, args=nil + -- --args element[{}]: details on one argument to a digshape command + -- -- --name[string]: name for this argument, used as in-game display + -- -- --required[bool]: does digshape require we pass this arg? Almost always true atm. + -- -- --desc[string]: short description of what this argument controls, used for in-game help. + -- -- --default[any valid OR nil OR {}]: the default value for this arg if not supplied by user. If a {}, then should contain validity checking: + -- -- -- --default=[any]: the actual default value, only if args.default={} + -- -- -- --min=[numeric]: lower (inclusive) bound on value. If not present, then value can be lowered to min(type) + -- -- -- --max=[numeric]: upper (inclusive) bound on value. If not present, then value can be raised to max(type) + -- -- -- --values=[{} OR string]: ordered list of ONLY acceptable values. If a string, treated as lua String.Pattern that must match entire value. + -- -- -- --type=[string: "int, float, bool, string, pos, luatable"]: what data type is acceptable, only one type is allowed. + -- -- -- --inc=[numeric]: what is the default +- amount to change this value by when adjusted by GUI. If values={}, increment must be int,and is the number of indicies to advance(% len). TODO: SHIFT-inc: *2, CTRL-inc: *5, ALT-inc: /10; modifiers stack. + --]] + + + current = { command = "circle", args = {} }, --current stores the currently active command, and it's arguments and values. + + --COMMAND={requireOrigin = false, requireMajor = false, requireZ = false, allowFilled = false, args = {name="",required=true,default=nil,type="",desc=""}, runSilent = true, digMode = nil, desc = "Set the origin"}, + + origin = { requireOrigin = false, requireMajor = false, requireZ = false, allowFilled = false, args = nil, runSilent = true, digMode = nil, desc = "Set the origin" }, + controla = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = true, args = nil, runSilent = false, digMode = "@", desc = "Set the first control point (A, or 'major')" }, + swap = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = true, args = nil, runSilent = false, digMode = "@", desc = "Swap the origin and cursor" }, + + circle = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = true, args = nil, runSilent = false, digMode = "@", desc = "Draw a circle" }, + + line = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = false, args = nil, runSilent = false, digMode = "@", desc = "Draw a line" }, + + ellipse = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = true, args = {}, runSilent = false, digMode = "@", desc = "Draw a ellipse" }, + + polygon = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = false, args = { { name = "sides", required = true, default = 3, type = "int", desc = "Number of sides of the polygon" }, { name = "vertex", required = true, default = false, type = "bool", desc = "Is the cursor on a vertex, or the midpoint of a side?" }, }, runSilent = false, digMode = "@", desc = "Draw a polygon" }, + + star = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = false, args = { { name = "points", required = true, default = 5, type = "int", desc = "Number of points of the star" }, { name = "skip", required = true, default = 2, type = "int", desc = "How many to skip when connecting...?" }, }, runSilent = false, digMode = "@", desc = "Draw a star" }, + + spiral = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = false, args = { { name = "coils", required = true, default = 2, type = "int", desc = "Number of turns the spiral makes." }, { name = "skip", required = true, default = 1, type = "int", desc = "Draw every # points along spiral." }, { name = "rotate", required = true, default = { default = 0, min = -360, max = 360, inc = 15, type = "int" }, type = "int", desc = "Rotate the spiral, 0-360." }, }, runSilent = false, digMode = "@", desc = "Draw a spiral" }, + + flood = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = true, args = { { name = "max", required = false, default = 10000, type = "int", desc = "Maximum number of tiles filled before aborting. Larger numbers just take longer to complete." }, { name = "diagonals", required = false, default = false, type = "bool", desc = "Should the flood escape through corners?" }, }, runSilent = false, digMode = "@", desc = "Floodfill current designation" }, + + resetz = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = true, args = nil, runSilent = false, digMode = "@", desc = "Move all control points to current z level" }, + radial = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = true, args = { { name = "ways", required = true, default = 3, type = "int", desc = "Number of radially symetrical points to draw." }, }, runSilent = false, digMode = "@", "Draw points with radial symmetry around origin" }, --todo: code this + curve = { requireOrigin = true, requireMajor = true, requireZ = true, allowFilled = false, args = { { name = "Sharpness", required = true, default = { default = 1.5, min = 0, max = 100, inc = 0.1, type = "float" }, type = "float", desc = "How strongly the curve is pulled towards the cursor" } }, runSilent = false, digMode = "@", desc = "Draw a curve (bezier) from origin to major pulled towards cursor" }, --todo: allow filled. Also draw line, then fill shape + --{ default = 1.5, min = 0, max = 100, inc = 0.1, type = "float" } + --arc = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = true, args = nil, runSilent = false,digMode="@",desc="Draw an arc from origin to major passing through cursor." }, + downstair = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = true, args = { { name = "depth", required = true, default = 1, type = "int", desc = "Number of z levels down to designate." }, { name = "start", required = true, default = true, type = "bool", desc = "Should the starting level be updown [false] or down [true]" }, }, runSilent = false, digMode = "@", desc = "Designate a 3x3 block of updown stairs, corners and center only" }, + }, --Mouse variables mouse = true, - mousebuttons={'commit','origin','major'}, + mousebuttons = { 'commit', 'origin', 'major' }, dragging = false, lastMouse = xyz2pos(0, 0, 0), lerp = true, --lerp is laggy because it repeatedly inefficiently accesses memory; it would be faster if we cached some but thats above my paygrade ;) customOffset = { x = 0, y = 0 }, } +function DigshapeUI:init() + self.saved_mode = df.global.ui.main.mode + df.global.ui.main.mode = df.ui_sidebar_mode.LookAround + + --grab the current digshape status + self:runDigshapeCommand("digshape lua status") + + --set up the layout of the menu + self:addviews { + + widgets.Label { + frame = { t = 3, l = 1 }, --place it inset one tile off the bottom left + view_id = "controlPointsMenu", + text = { + { key = "CUSTOM_O", text = "Set origin", key_sep = ": ", + on_activate = self:callback('buttonCallback_setOrigin') + }, NEWLINE, + + { key = "CUSTOM_S", text = "Swap origin/cursor", key_sep = ": ", + on_activate = self:callback('buttonCallback_swapOrigin') + }, NEWLINE, + + { key = "CUSTOM_A", text = "Set major", key_sep = ": ", + on_activate = self:callback('buttonCallback_setControlA') + }, NEWLINE, + + { id = "button_toggleFill", key = "CUSTOM_F", text = "Toggle fill: " .. self:getCurrentFill(), key_sep = ": ", + on_activate = self:callback('buttonCallback_toggleFilled') + }, NEWLINE, + }, + }, + + + widgets.Label { + frame = { t = 9, l = 1 }, --place it inset one tile off the bottom left + view_id = "digshapeMenu", + text = { + { key = "CUSTOM_P", text = "Set digshape command", key_sep = ": ", + on_activate = self:callback('buttonCallback_setCommand'), + }, NEWLINE, + { text = "[ digshape command ]", gap = 2, pen = COLOR_YELLOW, id = "label_digshapeCommand" }, NEWLINE, + NEWLINE, + + + --------------Arg 1 + { key = "SECONDSCROLL_UP", key_sep = ",", id = "button_arg1dec", + on_activate = self:callback('buttonCallback_argAdjust', 1, "-", 1) + + }, + { key = "SECONDSCROLL_DOWN", id = "button_arg1inc", + on_activate = self:callback('buttonCallback_argAdjust', 1, "+", 1) + + }, + { text = ": ", id = "label_arg1sepA", }, --Adjust: "}, + { text = "ARG1NAME", id = "label_arg1name", width = 8, pad_char = ".", }, + { text = ": [ ", id = "label_arg1sepB", }, + { text = "#", id = "label_arg1value" }, + { text = " ]", id = "label_arg1sepC", }, + NEWLINE, + + --------------Arg 2 + { key = "SECONDSCROLL_PAGEUP", key_sep = ",", id = "button_arg2dec", + on_activate = self:callback('buttonCallback_argAdjust', 2, "-", 1) + + }, + { key = "SECONDSCROLL_PAGEDOWN", id = "button_arg2inc", + on_activate = self:callback('buttonCallback_argAdjust', 2, "+", 1) + + }, + { text = ": ", id = "label_arg2sepA", }, --Adjust: "}, + { text = "ARG2NAME", id = "label_arg2name", width = 8, pad_char = ".", }, + { text = ": [ ", id = "label_arg2sepB", }, + { text = "#", id = "label_arg2value" }, + { text = " ]", id = "label_arg2sepC", }, + NEWLINE, + + --------------Arg 3 + { key = "STRING_A091", key_sep = ",", id = "button_arg3dec", --STANDARDSCROLL_UP + on_activate = self:callback('buttonCallback_argAdjust', 3, "-", 1) + + }, + { key = "STRING_A093", id = "button_arg3inc", + on_activate = self:callback('buttonCallback_argAdjust', 3, "+", 1) + + }, + { text = ": ", id = "label_arg3sepA", }, --Adjust: "}, + { text = "ARG3NAME", id = "label_arg3name", width = 8, pad_char = ".", }, + { text = ": [ ", id = "label_arg3sepB", }, + { text = "#", id = "label_arg3value" }, + { text = " ]", id = "label_arg3sepC", }, + NEWLINE, + + +--[[ { key = "CUSTOM_SHIFT_P", text = "Reset arguments", key_sep = ": ", id = "button_resetArgs", + on_activate = self:callback('buttonCallback_argAdjust', -1, "reset") + --self:callback('buttonCallback_setCommand')("Q1") + }, NEWLINE,]] + + } + }, + + widgets.Label { + frame = { t = 18, l = 1 }, + view_id = "digmodeMenu", + text = { + { text = "Set Designation:" }, + NEWLINE, + { text = "[" }, --Adjust: "},Designate: + { text = "", id = "label_digmodeStart" }, + { key = "CUSTOM_D", text = "", key_sep = "", + on_activate = self:callback('buttonCallback_setDig', 'd'), id = "button_digmode_d", + --pen=self.pens.digMode.selected,dpen=self.pens.digMode.deselected,enabled=true, + }, + { key = "CUSTOM_I", text = "", key_sep = "", + on_activate = self:callback('buttonCallback_setDig', 'i'), id = "button_digmode_i", + -- pen=self.pens.digMode.selected,dpen=self.pens.digMode.deselected,enabled=false, + }, + { key = "CUSTOM_H", text = "", key_sep = "", + on_activate = self:callback('buttonCallback_setDig', 'h'), id = "button_digmode_h", + --pen=self.pens.digMode.selected,dpen=self.pens.digMode.deselected,enabled=false, + }, + { key = "CUSTOM_R", text = "", key_sep = "", + on_activate = self:callback('buttonCallback_setDig', 'r'), id = "button_digmode_r", + -- pen=self.pens.digMode.selected,dpen=self.pens.digMode.deselected,enabled=false, + }, + { key = "CUSTOM_J", text = "", key_sep = "", + on_activate = self:callback('buttonCallback_setDig', 'j'), id = "button_digmode_j", + -- pen=self.pens.digMode.selected,dpen=self.pens.digMode.deselected,enabled=false, + }, + { key = "CUSTOM_U", text = "", key_sep = "", + on_activate = self:callback('buttonCallback_setDig', 'u'), id = "button_digmode_u", + -- pen=self.pens.digMode.selected,dpen=self.pens.digMode.deselected,enabled=false, + }, + { key = "CUSTOM_X", text = "", key_sep = "", + on_activate = self:callback('buttonCallback_setDig', 'x'), id = "button_digmode_x", + -- pen=self.pens.digMode.selected,dpen=self.pens.digMode.deselected,enabled=false, + }, + { key = "CUSTOM_SHIFT_M", text = "", key_sep = "", + on_activate = self:callback('buttonCallback_setDig', 'M'), id = "button_digmode_M", + -- pen=self.pens.digMode.selected,dpen=self.pens.digMode.deselected,enabled=false, + }, + --NEWLINE, + + -- { text = "ARG3NAME", id = "label_arg3name" }, + --{ text = "", id = "label_digmodeEnd" }, + { text = "]: ", pen = { CLEAR_PEN, bg = COLOR_BLACK }, }, + { text = "digmode name", id = "label_digmodeName" }, + --{ text = " ]" },NEWLINE, + }, + }, + + widgets.Label { + frame = { b = 1, l = 1 }, --place it inset one tile off the bottom left + view_id = "bottomMenu", + text = { + { key = "STRING_A092", text = "Move view to see origin", key_sep = ": ", + on_activate = self:callback('buttonCallback_recenterView'), + }, NEWLINE, + { key = "CUSTOM_Z", text = "Undo last digshape", key_sep = ": ", + on_activate = self:callback('buttonCallback_undo'), + }, NEWLINE, + + { key = "SELECT", text = "Execute Command", key_sep = ": ", + on_activate = self:callback('buttonCallback_commit'), + }, NEWLINE, + + { key = "LEAVESCREEN", text = "Back", key_sep = ": ", + on_activate = self:dismiss(), + }, NEWLINE, + }, + }, + } + + --if origin isn't set, just put it at the cursor + --todo: make this a skippable option based on argument. Other values might include "here", "last", "coords" + if self.origin == nil then + self:runDigshapeCommand("digshape lua origin") + end -local digButtons={ - {key='d', symbol=" ", text="Mine"}, - {key='i', symbol="X", text="U/D Stair"}, - {key='h', symbol="_", text="Channel"}, - {key='r', symbol=30, text="Up Ramp"}, - {key='j', symbol=">", text="Down Stair"}, - {key='u', symbol="<", text="Up Stair"}, - {key='x', symbol=" ", text="Remove Designation"}, - {key='M', symbol=" ", text="Toggle Dig/Mark"} -} + --assume we're always working on the current z level, todo: make this an option based on argument + self.autosetZtoCurrent = true -local digModeToButton = {} -for _, data in pairs(digButtons) do - data.keybind = ("CUSTOM_".. (string.match(data.key,"%u") ~=nil and "SHIFT_" or "") .."%s"):format(data.key:upper()) - digModeToButton[data.key] = data - print("~") + --display the current command preview + self:buttonCallback_setDig(self.activeDesgination) + self:setCommand(self.currentCommand) + self:previewCurrentCommand() + --dfhack.gui.revealInDwarfmodeMap(self.origin) end -local buttons = { - --key: the key that will be bound to callback. For non-button entries (eg blank lines), set key="NOTKEY" to skip the keybinding. - --text: the label for the keybind - --prelabel: a menu rendering function exicuted before displaying the keybinding, use to newline or set color, etc - --postlabel: as prelabel, but after the keybinding label is printed. - --callback: the function called by this button - {key="p", text="Set digshape command", postlabel=function(self, dc) - dc:newline(1):advance(3):pen(COLOR_YELLOW):string("[ "..self.currentCommand.." ]") - end, callback=function(self) - dialog.showInputPrompt("Set digshape command", "Enter a digshape command", COLOR_WHITE, self.currentCommand, function(result) - self.currentCommand=result - self:runCurrentCommand(true) - end) - end}, - {key="o", text="Set origin",prelabel=function(self,dc)dc:newline(1) if self.origin ~=nil then dc:pen(COLOR_LIGHTGREEN) else dc:pen(COLOR_LIGHTRED) end end, callback=function(self) - dfhack.run_command_silent("digshape lua origin") - self:runCurrentCommand(true) - end}, - {key="s", text="Swap origin/cursor", prelabel=function(self,dc)dc:newline(1):advance(2) if self.origin ~=nil then dc:pen(COLOR_LIGHTGREEN) else dc:pen(COLOR_LIGHTRED) end end, callback=function(self) - dfhack.run_command_silent("digshape lua swap") - self:runCurrentCommand(true) - end}, - - {key="a", text="Set major", callback=function(self) - dfhack.run_command_silent("digshape lua major") - self:runCurrentCommand(true) - end}, - {key="NOTKEY",prelabel=function(self,dc)dc:newline(2) end}, - {key="SELECT", keybind="SELECT", text="Execute command", callback=function(self) - self:runCurrentCommand(false) - end}, - {key="z", text="Undo digshape command", callback=function(self) - dfhack.run_command("digshape undo") - end} -} -for _, data in pairs(buttons) do - data.keybind = data.keybind or ("CUSTOM_%s"):format(data.key:upper()) +function DigshapeUI:onDestroy() + df.global.ui.main.mode = self.saved_mode +end + +function DigshapeUI:toggleSubViewVis(viewID, setActiveByVis) + --toggles visibility (and by default, active status) of a id'd subview. + if self.subviews == nil then + stdout("ERR", "View element not found:", viewID) + return + end + if setActiveByVis == nil then + setActiveByVis = true + end + --self.subviews[viewID]=nil + --for i,v in ipairs(self.subviews) do + -- if v.view_id==viewID then + -- self.subviews[i]=nil + -- end + --end + -- + self.subviews[viewID].visible = not self.subviews[viewID].visible + if setActiveByVis then + self.subviews[viewID].active = self.subviews[viewID].visible + end +end + +function DigshapeUI:toggleSubViewActive(viewID) + --toggles View.Active, which can change the display, and stops it from getting keypresses. + if self.subviews == nil then + stdout("ERR", "View element not found:", viewID) + return + end + self.subviews[viewID].active = not self.subviews[viewID].active end local lastX = df.global.cursor.x local lastY = df.global.cursor.y local lastZ = df.global.cursor.z + + +--local function deepcopy(orig) +--http://lua-users.org/wiki/CopyTable +-- local orig_type = type(orig) +-- local copy +-- if orig_type == 'table' then +-- copy = {} +-- for orig_key, orig_value in next, orig, nil do +-- copy[deepcopy(orig_key)] = deepcopy(orig_value) +-- end +-- setmetatable(copy, deepcopy(getmetatable(orig))) +-- else -- number, string, boolean, etc +-- copy = orig +-- end +-- return copy +--end + + +function DigshapeUI:parseCommand() + stdout("MSG", "parsecommand:", self.currentCommand) + --if self.parsedCommand == nil then + local commandBase = self.currentCommand:lower():match("^%a+") + self.parsedCommand = commandBase + self.digshapeCommands.current = { command = commandBase, args = {} } + + local args = { commandargs = self.digshapeCommands[commandBase].args, --copyall() the command specific arguments (eg "chords" for digshape spiral). We use copyall to get a copy so it remains unchanged. + genericargs = { + fill = "NA", --NA if unsupported by this command, "filled" or "hollow" if digshape supports it for this command + digmode = "@", --'@': replace with current digmode. + mode = "designating"--"designating" or "marking" or "toggling" + } } + if self.digshapeCommands[commandBase].allowFilled then + args.genericargs.fill = self.designateFilled and "filled" or "hollow" + end + -- if self.digshapeCommands[commandBase].digMode ~= nil then + args.genericargs.digmode = self.digshapeCommands[commandBase].digMode + -- end + --print("1>") for k,v in pairs(args.commandargs) do print(" 1>"..k..":"..v) end for k,v in pairs(args.genericargs) do print(" 2>"..k..":"..v) end + + + --local buildCommand = { + -- --for each digshape command, setup it's arg string, and TODO: check that it's conditions are met. + -- circle = function(self, args) + -- local temp = self.digshapeCommands[self.digshapeCommands.current.command].allowFilled and self.designateFilled and "filled" or "hollow" + -- args.genericargs.fill = temp + -- end, + -- origin = function(self, args) + -- end, + --} + --buildCommand[commandBase](self, args) + --print("2>") for k,v in pairs(args.commandargs) do print(" 1>"..k..":"..v) end for k,v in pairs(args.genericargs) do print(" 2>"..k..":"..v) end + if args.commandargs ~= nil then + --self.digshapeCommands[self.digshapeCommands.current.command].args ~= nil then + --print(">", self.digshapeCommands.current.command) + for argi = 1, #args.commandargs do + --for k, _ in pairs(self.digshapeCommands[self.digshapeCommands.current.command].args) do + -- print("->", argi) + if args.commandargs[argi].currentValue == nil then + args.commandargs[argi].currentValue = args.commandargs[argi].default + end + if type(args.commandargs[argi].currentValue) == "table" then + args.commandargs[argi].currentValue = args.commandargs[argi].default.default + --todo: delete this once all the commands have full default={} stuff. + end + + self.subviews.digshapeMenu.text_ids["label_arg" .. argi .. "value"].text = tostring(args.commandargs[argi].currentValue) + self.subviews.digshapeMenu.text_ids["label_arg" .. argi .. "name"].text = args.commandargs[argi].name + self.parsedCommand = self.parsedCommand .. " " .. tostring(args.commandargs[argi].currentValue) + end + end + if args.genericargs.fill ~= "NA" then + self.parsedCommand = self.parsedCommand .. " " .. args.genericargs.fill + end + if args.genericargs.digmode ~= nil then + self.parsedCommand = self.parsedCommand .. " " .. args.genericargs.digmode:gsub("@", self.activeDesgination) + + end + + self.digshapeCommands.current.args = args + -- end + + self.subviews.digshapeMenu.text_ids.label_digshapeCommand.text = "[ " .. self.parsedCommand .. " ]" + + return commandBase +end + function DigshapeUI:runCurrentCommand(preview) - local command = ("digshape lua %s%s"):format(preview and "preview " or "", self.currentCommand):gsub("@", self.activeDesgination) - print(("command='%s'"):format(command)) + local command = ("digshape lua %s"):format(preview and "preview" or "") + local baseCommand = self:parseCommand() + command = command .. " " .. self.parsedCommand + + stdout("CMD", command, preview) + + --check to make sure digshape will like the command, if not, don't bother calling and just return. + local commandTests = self.digshapeCommands[baseCommand] + if baseCommand == nil then + stdout("WRN", "Current command will not exicute, skipping. 1") + return + end + if commandTests.requireOrigin == true and self.origin == nil then + stdout("WRN", "Current command will not exicute, skipping. 2") + --try and recover by asking digshape + self:runDigshapeCommand("digshape lua status") + if commandTests.requireOrigin == true and self.origin == nil then + self:runDigshapeCommand("digshape lua origin") + --worms("Hard fail to debug. Function intentionally does not exist. Nondebug: just return nil.") + return nil + end + end + if commandTests.requireMajor == true and self.major == nil then + stdout("WRN", "Current command will not exicute, skipping. 3") + return nil + end + --if commandTests.requireZ==true and self.origin==nil then return nil end + if commandTests.digMode ~= "@" and commandTests.digMod ~= self.activeDesgination then + stdout("WRN", "Current command will not exicute, skipping. 4") + return nil + end + + self:runDigshapeCommand(command) +end + +function DigshapeUI:runDigshapeCommand(command) + --simple validity checks + stdout("CMD", "RUN:", command) + if command == nil then + print("nil command") + return nil + end + local output = dfhack.run_command_silent(command) self.currentOutput = {} self.currentError = {} self.currentDig = {} self.origin = nil self.major = nil - print("output=", output) + for line in output:gmatch("[^\r\n]+") do + stdout("RBY", ">>" .. line) messageType = line:match("^([^:]+):") if messageType == "msg" then messageContents = line:match("^msg:(.*)$") table.insert(self.currentOutput, messageContents) elseif messageType == "err" then messageContents = line:match("^err:(.*)$") + if line:match("Origin and target must be on the same z") then + stdout("CMD", "-------------------------------RECURSIVELY RESETZ-----------------------") + self:runDigshapeCommand("digshape lua resetz") + self:runDigshapeCommand(command) + end table.insert(self.currentError, messageContents) elseif messageType == "dig" then + --these are the designations from digshape digMode, x, y, z = line:match("^dig:([^:]+):([^:]+):([^:]+):([^:]+)") - table.insert(self.currentDig, {digMode=digMode, x=tonumber(x), y=tonumber(y), z=tonumber(z), symbol=digModeToButton[digMode].symbol}) + table.insert(self.currentDig, { digMode = digMode, x = tonumber(x), y = tonumber(y), z = tonumber(z), symbol = self.digButtons[digMode].symbol }) elseif messageType == "pos" then posname, x, y, z = line:match("^pos:([^:]+):%(([^,]+),([^,]+),([^,]+)%)") x, y, z = tonumber(x), tonumber(y), tonumber(z) @@ -140,144 +621,348 @@ function DigshapeUI:runCurrentCommand(preview) self.origin = xyz2pos(x, y, z) elseif posname == "major" then self.major = xyz2pos(x, y, z) + --elseif posname == "cursor" then + -- self.cursor = xyz2pos(x, y, z) end - + elseif messageType == "ref" then + messageContents = line:match("^ref:(.*)$") + table.insert(self.currentOutput, messageContents) + stdout("RBY", "ref:", messageContents) else - print("unhandled output:", line) + -- stdout("ERR", "Digshape Unhandled Output:", line) + end + end + + if self.autosetZtoCurrent then + local currentz = df.global.cursor.z + for i = 1, #self.currentDig do + self.currentDig[i].z = currentz + end + if self.origin ~= nil then + self.origin.z = currentz + end + if self.major ~= nil then + self.major.z = currentz end end end -function DigshapeUI:init() - self.saved_mode = df.global.ui.main.mode - df.global.ui.main.mode=df.ui_sidebar_mode.LookAround - self:runCurrentCommand(true) +function DigshapeUI:previewCurrentCommand() + self.runCurrentCommand(self, true) end -function DigshapeUI:onDestroy() - df.global.ui.main.mode = self.saved_mode +function DigshapeUI:commitCurrentCommand() + self.runCurrentCommand(self, false) end local function paintMapTile(dc, vp, cursor, pos, ...) if not same_xyz(cursor, pos) then local stile = vp:tileToScreen(pos) - if stile.z == 0 then -- FIXME: reduce lag by increasing overlay - dc:map(true):seek(stile.x,stile.y):char(...):map(false) + if stile.z == 0 then + -- FIXME: reduce lag by increasing overlay + dc:map(true):seek(stile.x, stile.y):char(...):map(false) + end + end +end + +function DigshapeUI:buttonCallback_setOrigin() + stdout("MSG", ">setorigin>") + self:runDigshapeCommand("digshape lua origin") + + self:previewCurrentCommand() +end + +function DigshapeUI:buttonCallback_swapOrigin() + self:runDigshapeCommand("digshape lua swap") + self:previewCurrentCommand() + + +end + +function DigshapeUI:buttonCallback_setControlA() + self:runDigshapeCommand("digshape lua major") + self:previewCurrentCommand() +end + +function DigshapeUI:buttonCallback_toggleFilled() + self.designateFilled = not self.designateFilled + self.parsedCommand = nil + self:previewCurrentCommand() + + self.subviews.controlPointsMenu.text_ids.button_toggleFill.text = "Toggle fill: " .. self:getCurrentFill() +end + +function DigshapeUI:setCommand(newCommand) + local showArgs + showArgs = function(self, command) + -- stdout("MSG", ">showargs> " .. command) + -- print(self.currentCommand) + local args = self.digshapeCommands[command].args + local ids = { "button_arg#dec", "button_arg#inc", "label_arg#name", "label_arg#sepA", "label_arg#sepB", "label_arg#sepC", "label_arg#value"}--, "button_resetArgs" }--,"label_arg#desc"} + + local nargs = 0 + if args ~= nil then + for i, v in ipairs(args) do + for k = 1, #ids do + local index = string.gsub(ids[k], "#", i) + -- print("vis", i, k, index) + local temp = self.subviews.digshapeMenu.text_ids[index] + temp.disabled = false + end + nargs = nargs + 1 + end + end + + if nargs < 3 then + for i = nargs + 1, 3 do + for k = 1, #ids do + local index = string.gsub(ids[k], "#", i) + -- print("hide", i, k, index) + local temp = self.subviews.digshapeMenu.text_ids[string.gsub(ids[k], "#", i)] + temp.dpen = CLEAR_PEN + temp.disabled = true + end + self.subviews.digshapeMenu.text_ids["label_arg" .. i .. "name"].text = "--------" + self.subviews.digshapeMenu.text_ids["label_arg" .. i .. "value"].text = "-" + end + + end + end + self.currentCommand = newCommand + self.parsedCommand = nil + local command = self:parseCommand() + self:runDigshapeCommand("digshape lua status") + showArgs(self, command)--showargs first so that preview updates their values. + self:previewCurrentCommand() + +end + +function DigshapeUI:buttonCallback_setCommand() + --TODO: can maybe fix the stupid transparent edit box by making own class that supers all except changes the root gui:framedScreen.frame_background pen to not CLEAR_PEN.... or maybe editfield.on_char or on_change + + + dialog.showInputPrompt("Set digshape command", "Enter a digshape command", COLOR_WHITE, "", function(result) + self:setCommand(result) + end + ) + + +end + +function DigshapeUI:buttonCallback_setDig(mode) + local buttonCallback_setDig_labelhelper + buttonCallback_setDig_labelhelper = function(mode, set) + set = set == "set" and "set" or "clear" + --In the button list, insert a > and < before and after the currently selected item. + + local pitem = string.match(self.digButtons.order, "(.)" .. mode) + local titem = "button_digmode_" .. mode + + if pitem == "_" then + --we're outside the list of actual buttons, use the text []. + pitem = "label_digmodeStart" + else + pitem = "button_digmode_" .. pitem + end + + local kpen = self.pens.digMode.deselected + local text = "" + + if set == "set" then + local temp = self.pens.digMode.selected + if mode == 'x' then + temp = self.pens.digMode.delete + elseif mode == 'M' then + temp = self.pens.digMode.mark + end + kpen = temp + end + + local doset + doset = function(label, kpen, text) + --for k, v in pairs(self.subviews.digmodeMenu.text_ids) do + -- print(k, v) + --end + + if text == "<" then + self.subviews.digmodeMenu.text_ids[label].key_pen = kpen + end + self.subviews.digmodeMenu.text_ids[label].pen = kpen + self.subviews.digmodeMenu.text_ids[label].text = " "--text + end + + doset(pitem, kpen, ">")--before + doset(titem, kpen, "<")--thisitem + end + + buttonCallback_setDig_labelhelper(self.activeDesgination, "clear") + buttonCallback_setDig_labelhelper(mode, "set") + self.subviews.digmodeMenu.text_ids["label_digmodeName"].text = self.digButtons[mode].text + + --do the update: + self.activeDesgination = mode + self.parsedCommand = nil --regen digshape command + self:parseCommand() + self:previewCurrentCommand() +end + +function DigshapeUI:getCurrentFill() + local value = "" + + if self.digshapeCommands[self.digshapeCommands.current.command].allowFilled == false then + value = "NA" + elseif self.designateFilled then + value = "Filled" + else + value = "Hollow" + end + return value +end + +function DigshapeUI:buttonCallback_argAdjust(argNum, argDir, argMod) + --TODO: make these curried as ARG#, INC/DEC={"+", "-"}, MODIFIER?S? + -- --(TODO: argMod:: SHIFT-inc: *2, CTRL-inc: *5, ALT-inc: /10; modifiers stack. ) + + local tempDir = 1 + if argDir == "-" then + tempDir = tempDir * -1 + end + local tempMod = 1 + + local arg = self.digshapeCommands.current.args.commandargs[argNum] + if argDir == "reset" then + stdout("MSG","Reset all args to default.") + self:setCommand(self.digshapeCommands.current.command) + return + end + + if type(arg.default) == "table" then + tempMod = tempMod * arg.default.inc + end + tempMod = tempMod * (argMod) + + stdout("argAdj: ", argNum, ") ", tempDir, tempMod) + + if type(arg.currentValue) == "boolean" then + arg.currentValue = not arg.currentValue + else + local newval = self.digshapeCommands.current.args.commandargs[argNum].currentValue + (1 * tempDir * tempMod) + if type(arg.default) == "table" then + if newval < arg.default.min then + newval = arg.default.min + elseif newval > arg.default.max then + newval = arg.default.max + end end + self.digshapeCommands.current.args.commandargs[argNum].currentValue = newval end + self:previewCurrentCommand() +end + +-- +--function DigshapeUI:buttonCallback_() +-- +--end + +function DigshapeUI:buttonCallback_undo() + dfhack.run_command("digshape undo") end +function DigshapeUI:buttonCallback_commit() + self:commitCurrentCommand() +end + +--function DigshapeUI:buttonCallback_setCommand() +-- +--end + + + +function DigshapeUI:buttonCallback_recenterView() + stdout("MSG", "recenter view:", self.origin) + dfhack.gui.revealInDwarfmodeMap(self.origin) +end +--function DigshapeUI:buttonCallback_() +-- +--end +-- +--function DigshapeUI:buttonCallback_() +-- +--end +--function DigshapeUI:buttonCallback_() +-- +--end + + + + + + + + + + + + + + + function DigshapeUI:renderOverlay() - local vp=self:getViewport() + --todo: consider --https://docs.dfhack.org/en/stable/docs/Lua%20API.html#penarray-class for speedup + local vp = self:getViewport() local dc = gui.Painter.new(self.df_layout.map) local visible = gui.blink_visible(500) local cursorX, cursorY, cursorZ = df.global.cursor.x, df.global.cursor.y, df.global.cursor.z - if lastX ~= cursorX or lastY ~= cursorY or lastZ ~= cursorZ then + if lastX ~= cursorX or lastY ~= cursorY or lastZ ~= cursorZ then lastX, lastY, lastZ = cursorX, cursorY, cursorZ - self:runCurrentCommand(true) + --we have moved cursor, so update the state of the preview + self:previewCurrentCommand() end for _, dig in ipairs(self.currentDig) do - paintMapTile(dc, vp, df.global.cursor, xyz2pos(dig.x, dig.y, dig.z), dig.symbol, self.activeDesgination=='x' and self.pens['clear'] or self.pens['designation']) + paintMapTile(dc, vp, df.global.cursor, xyz2pos(dig.x, dig.y, dig.z), dig.symbol, self.activeDesgination == 'x' and self.pens['clear'] or self.pens['designation']) end if self.origin then paintMapTile(dc, vp, df.global.cursor, self.origin, '+', self.pens['origin']) end - if self.major then - paintMapTile(dc, vp, df.global.cursor, self.major, '+', self.pens['major']) + if self.digshapeCommands.current.command~=nil then + if self.major then + if self.digshapeCommands[self.digshapeCommands.current.command].requireMajor then + paintMapTile(dc, vp, df.global.cursor, self.major, 'a', self.pens['ctrl_A']) + + end + end + end - - + + end function DigshapeUI:onRenderBody(dc) self:renderOverlay() - dc:clear():seek(1, 1):pen(COLOR_WHITE):string("Digshape - " .. self.state--:gsub("^%a", function(x) - -- return x:upper() - -- end) - ) - dc:seek(1,3) - - if true or self.state=="preview" then - for _, data in pairs(digButtons) do - builder = dc:newline(1):key_string(data.keybind, data.text, self.activeDesgination==data.key and COLOR_WHITE or COLOR_GREY) - -- if data.key=='x' then - -- builder:newline(1) - -- end - end + dc:clear():seek(1, 1):pen(COLOR_WHITE):string("Digshape - " .. self.state) - dc:newline(2) - - for _, data in pairs(buttons) do - if data.prelabel ~= nil then - data.prelabel(self,dc) - else - dc:newline(1):pen(COLOR_GREY) - end - if data.key~="NOTKEY" then - builder = dc:key_string(data.keybind, data.text) - end - if data.postlabel ~= nil then - data.postlabel(self,dc) - end - --if data.key=='m' then - --builder:newline(1) - --end - end - - - --[[ dc:key_string("CUSTOM_S", "Set Brush",COLOR_GREY) - dc:newline():newline(1) - dc:key_string("CUSTOM_H", "Flip Horizontal",COLOR_GREY):newline(1) - dc:key_string("CUSTOM_V", "Flip Vertical",COLOR_GREY):newline(1) - dc:key_string("CUSTOM_R", "Rotate 90",COLOR_GREY):newline(1) - dc:key_string("CUSTOM_T", "Rotate -90",COLOR_GREY):newline(1) - dc:key_string("CUSTOM_G", "Cycle Corner",COLOR_GREY):newline(1) - dc:key_string("CUSTOM_I", "Invert",COLOR_GREY):newline(1) - dc:key_string("CUSTOM_C", "Convert to...",COLOR_GREY):newline(1) - dc:newline(1) - dc:key_string("CUSTOM_E", (self.option=="erase" and "Erasing" or "Erase"),self.option=="erase" and COLOR_RED or COLOR_GREY):newline(1) --make red - dc:key_string("CUSTOM_X", (self.option=="construction" and "Removing" or "Remove").." Constructions",self.option=="construction" and COLOR_GREEN or COLOR_GREY):newline(1) --make red - dc:newline():newline(1) - dc:key_string("CUSTOM_B", "Blink Brush",self.blink and COLOR_WHITE or COLOR_GREY):newline(1) - dc:newline() ]] - end - - dc:newline():newline(1):key_string("LEAVESCREEN", "Back") end - function DigshapeUI:onInput(keys) - if df.global.cursor.x==-30000 then - local vp=self:getViewport() - df.global.cursor=xyz2pos(math.floor((vp.x1+math.abs((vp.x2-vp.x1))/2)+.5),math.floor((vp.y1+math.abs((vp.y2-vp.y1)/2))+.5), vp.z) + + if df.global.cursor.x == -30000 then + local vp = self:getViewport() + df.global.cursor = xyz2pos(math.floor((vp.x1 + math.abs((vp.x2 - vp.x1)) / 2) + .5), math.floor((vp.y1 + math.abs((vp.y2 - vp.y1) / 2)) + .5), vp.z) return end - for k,v in pairs(keys) do + + for k, v in pairs(keys) do if k:match("^A_MOVE_") then self.refresh = 1 end end - if true or self.state=="preview" then - for _, data in ipairs(digButtons) do - if keys[data.keybind] then - self.activeDesgination = data.key - self:runCurrentCommand(true) - end - end - for _, data in ipairs(buttons) do - if keys[data.keybind] then - data.callback(self) - end - end - if keys.SELECT then - --self:pasteBuffer(copyall(df.global.cursor)) - end - end + + DigshapeUI.super.onInput(self, keys) --call super so subviews (eg. the widges.Labels) can capture keypresses too. if keys.LEAVESCREEN then self:dismiss() @@ -286,9 +971,9 @@ function DigshapeUI:onInput(keys) end end -if not (dfhack.gui.getCurFocus():match("^dwarfmode/Default") or dfhack.gui.getCurFocus():match("^dwarfmode/Designate") or dfhack.gui.getCurFocus():match("^dwarfmode/LookAround"))then +if not (dfhack.gui.getCurFocus():match("^dwarfmode/Default") or dfhack.gui.getCurFocus():match("^dwarfmode/Designate") or dfhack.gui.getCurFocus():match("^dwarfmode/LookAround")) then qerror("This screen requires the main dwarfmode view or the designation screen") end -local list = DigshapeUI{state="mark", blink=false,cull=true} +local list = DigshapeUI { state = "mark", blink = false, cull = true } list:show() \ No newline at end of file From bb54bd0546fc8f18d111b0fd8e6c5e5f99f70374 Mon Sep 17 00:00:00 2001 From: Quatch Date: Wed, 31 Mar 2021 20:04:04 -0400 Subject: [PATCH 08/12] Fixed ctrlptA display/toggle -now respect allowfilled for menu display -now respect requireMajor for display/menudisplay -spiral rotation is now modulo 360 -refactored self. "command", "activecommand", "parsedcommand", self.digshapecommands. "current" --> to a new self.activecommand with less confusing parts. "name" is the base name of the command (ellipse, circle, etc), "args" are the commandargs for the specific digshape command, "digshapeArgs" are the generic digshape args (filled, digmode), "digshapeString" is the fully composed string to send to digshape (which will include nondigshape args until stripped in rundigshapecommand() ) -significantly revised program flow to be simpler and make less useless update/redraw calls. -added aliases to commands, mostly 1 letter shortcuts, as a bandaid until proper selection --- digshape.rb | 5 +- gui/digshape.lua | 741 ++++++++++++++++++++++++++++++++++++----------- 2 files changed, 574 insertions(+), 172 deletions(-) diff --git a/digshape.rb b/digshape.rb index e533cc6..5db2ceb 100644 --- a/digshape.rb +++ b/digshape.rb @@ -1469,7 +1469,7 @@ def noMoreArguments(args) drawEllipse($origin.x, $origin.y, $origin.z, df.cursor.x, df.cursor.y, df.cursor.z, x2=nil, y2=nil, z2=nil, filled=filled, digMode=digMode, mode='bbox') # fixme: default arguments should be colon not equals - when 'circle2p', 'circle', 'c' #digshape circle2p [filled] [digmode] + when 'circle2p', 'circle', 'c','c2' #digshape circle2p [filled] [digmode] filled = getFilledArgument($script_args) digMode = getDigModeArgument($script_args) noMoreArguments($script_args) @@ -1478,7 +1478,7 @@ def noMoreArguments(args) drawEllipse($origin.x, $origin.y, $origin.z, df.cursor.x, df.cursor.y, df.cursor.z, x2=nil, y2=nil, z2=nil, filled=filled, digMode=digMode, mode='diameter') # fixme: default arguments should be colon not equals - when 'ellipse3p', 'e3p' #digshape ellipse3p [filled] [digmode] + when 'ellipse3p', 'e3p','e3' #digshape ellipse3p [filled] [digmode] filled = getFilledArgument($script_args) digMode = getDigModeArgument($script_args) noMoreArguments($script_args) @@ -1540,6 +1540,7 @@ def noMoreArguments(args) requireOriginZLevel() drawSpiral($origin.x, $origin.y, $origin.z, df.cursor.x, df.cursor.y, df.cursor.x, coils, chord, rotate, digMode) + # todo: spiral with negative turns should twist other way. when 'keupo', 'stairs', 'downstairs', 'downstair' #digshape keupo depth depth = getIntegerArgument($script_args, type: "depth") diff --git a/gui/digshape.lua b/gui/digshape.lua index f597fc2..5ea252e 100644 --- a/gui/digshape.lua +++ b/gui/digshape.lua @@ -4,10 +4,54 @@ gui/digshape =========== gui front-end for digshape.rb, a geometric designations generating tool + +--todo: mousemode. mouse: click1 set origin; click1 again: set cursor, commmit ]====] verbose = false --TODO: move these down later to be arguments --dfhack.screen.invalidate() --force an immediate redraw. +--TODO: use dfhack.print(args...) for better printing + +function printtable(table, note, recursecount, recurselimit) + --pretty print a table, adds $note and source line number as a header + --printall_recurse(obj)..sigh. Why couldn't I find this when I went looking for it. + if not verbose then + return + end + local prefix = note or "|" + local recursecount = recursecount or 5 + local title = "" + if not string.match(prefix, "^ *|$") then + title = " " .. prefix .. " " + prefix = "|" + end + + if recursecount <= 0 then + print(prefix .. "-->> MAX DEPTH <<--") + return + end + + if prefix == "|" then + dfhack.color(COLOR_LIGHTGREEN)--reset colour + dfhack.println("=PRINT=TABLE=" .. title .. "============ <>") + dfhack.color(nil)--reset colour + end + if type(table) ~= "table" then + print("->>" .. prefix .. table) + return + end + print(string.gsub(prefix, "|", "") .. "@>-----------------@") + for k, v in pairs(table) do + if type(v) ~= "table" then + print(prefix .. "[" .. k .. "]: <" .. tostring(v) .. ">") + else + print(prefix .. "[" .. k .. "]: ")--.. ":") + printtable(v, " " .. prefix, recursecount - 1) --indent prefix and recurse. Yes, no sanity checking, user knows when to use, not for production. + end + end + print(string.gsub(prefix, "|", "") .. "@<-----------------@") + dfhack.color(nil)--reset colour +end --[[ =======Useful dfhack tidbits======== @@ -66,35 +110,57 @@ local guidm = require "gui.dwarfmode" local dialog = require "gui.dialogs" local widgets = require 'gui.widgets' -stdout = function(...) -end--silently discard +stdout = function(msgtype, ...) + if type(msgtype) == "string" then + local _, _, temp = string.find(msgtype, "^(...)$") + if temp == "OUT" then + --only print the final output + dfhack.color(COLOR_WHITE) + dfhack.print(msgtype:sub(4)) + dfhack.println(...) + dfhack.color(nil) + end + end +end--silently discard everything but output. if verbose then --atm verbose must be set manually, see line2 of this script. dfhack.console.clear() stdout = function(msgtype, ...) --A stupid pretty console print command. - local prefix = "" + local prefix = "DS/UI" .. "(" .. debug.getinfo(2).currentline .. "): " if type(msgtype) == "string" then - _, _, temp = string.find(msgtype, "^(...)$") - if temp == "ERR" or temp == "MSG" or temp == "WRN" or temp == "OUT" or temp == "CMD" or temp == "RBY" then + local _, _, temp = string.find(msgtype, "^(...)$") + if temp == "ERR" or temp == "MSG" or temp == "WRN" or temp == "OUT" or temp == "CMD" or temp == "RBY" or temp == ">>>" or temp == "CAL" then --output channels: MSG/WRN/ERR: information, OUT: results, RBY: digshape output passed through. - if temp == "IGNORE" or temp == "ALSOIGNORE" or temp == "RBlY" then + if temp == "IGNORE" or temp == "ALSOIGNORE" or temp == "RBY" then return --don't print end + local color = { RBY = COLOR_DARKGREY, MSG = COLOR_WHITE, WRN = COLOR_YELLOW, ERR = COLOR_LIGHTRED, CMD = COLOR_LIGHTCYAN, OUT = COLOR_MAGENTA, [">>>"] = COLOR_LIGHTMAGENTA, CAL = COLOR_BROWN } + dfhack.color(color[temp]) if verbose == false and temp ~= "ERR" then return --only print errors when not verbose end - prefix = " DS: " .. msgtype .. ": " + --[[ if temp=="CAL" then + temp= + if temp ~=nil then + msgtype=msgtype.."@ "..temp + + end + end]] + + prefix = prefix .. msgtype .. ": " msgtype = "" - else - prefix = " DS: " + --else + -- prefix = " DS: " end end - print(prefix, msgtype, ...) + dfhack.print(prefix .. msgtype)-- + dfhack.println(...) + dfhack.color(nil)--reset colour end --TOOD: fancy! https://www.lua.org/pil/6.html else @@ -106,17 +172,33 @@ end --stdout("ERR", "A", "B", "C","D".."D") --stdout("A", "B", "C","D".."D") +--[[ TODO: split DigshapeUI into UI and I: +DigshapeInterface = defclass(DigshapeInterface) --a class to hold the digshape specific stuff, the translation and parsing stuff, rather than the UI/display stuff +This may get us a readOnly attr set for each command/command specific persistance if we make a new copy of this for each command. + +DigshapeInterface.ATTRS {} +DigshapeInterface:nameCommand(string)--extract the name command (eg "digshape lua preview ellipse bbox hollow d" -> "ellipse"), should this check the commandBase arg to translate? +DigshapeInterface:parseCommand(string) -- strip any UI only args, put into form that digshape will accept. Add "digshape lua", "preview" or not should be done by UI. +DigshapeInterface:runDigshapeCommand() +]] + + + DigshapeUI = defclass(DigshapeUI, guidm.MenuOverlay) DigshapeUI.ATTRS { state = "preview", activeDesgination = 'd', - currentCommand = 'spiral', --TODO: replace this with self.digshapeCommands.current - parsedCommand = DEFAULT_NIL, --TODO: move this to self.digshapeCommands.current.parsedCommand - - currentOutput = {}, - currentError = {}, - currentDig = {}, + activeCommand = { name = "spiral", args = {}, digshapeArgs = { fill = "NA", digmode = "@", mode = "designating" }, digshapeString = "" }, + resetActiveCommand = function() + return { name = "spiral", args = {}, digshapeArgs = { fill = "NA", digmode = "@", mode = "designating" }, digshapeString = "" } + end, + --currentCommand = 'spiral', --TODO: replace this with self.activeCommand.name + --parsedCommand = DEFAULT_NIL, --TODO: move this to self.activeCommand.name.parsedCommand + + --currentOutput = {}, + --currentError = {}, + --currentDig = {}, origin = nil, major = nil, autosetZtoCurrent = true, @@ -129,18 +211,26 @@ DigshapeUI.ATTRS { --{key, symbol character code, fgcolor, bgcolor} --https://docs.dfhack.org/en/stable/docs/Lua%20API.html#pen-api + --tiles and map icons origin = dfhack.pen.make({ ch = '+', fg = COLOR_CYAN, bg = COLOR_LIGHTGREEN }), cursor = dfhack.pen.make({ ch = 'X', fg = COLOR_CYAN, bg = COLOR_BLACK }), ctrl_A = dfhack.pen.make({ ch = 'a', fg = COLOR_CYAN, bg = COLOR_LIGHTCYAN }), designation = dfhack.pen.make({ fg = COLOR_BROWN, bg = COLOR_YELLOW }), mark = dfhack.pen.make({ fg = COLOR_YELLOW, bg = COLOR_LIGHTCYAN }), clear = dfhack.pen.make({ fg = COLOR_BROWN, bg = COLOR_RED }), + + --digmode menu digMode = { selected = dfhack.pen.make({ fg = COLOR_BLACK, bg = COLOR_LIGHTGREEN }), deselected = dfhack.pen.make({ fg = COLOR_LIGHTGREEN, bg = COLOR_BLACK }), delete = dfhack.pen.make({ fg = COLOR_BLACK, bg = COLOR_LIGHTRED }), mark = dfhack.pen.make({ fg = COLOR_BLACK, bg = COLOR_LIGHTCYAN }), }, + + --other menu + enabledMenu = dfhack.pen.make({ fg = COLOR_GREY, bg = COLOR_BLACK }), --visible and interactable + disabledMenu = CLEAR_PEN, --visible but locked + alertMenu = dfhack.pen.make({ fg = COLOR_DARKGREY, bg = COLOR_RED }), --visible and redbackground for required input }, --Designation mode variables (to be saved between commands) @@ -168,6 +258,9 @@ DigshapeUI.ATTRS { digshapeCommands = { --[[ --This table records the names, requiremens, and arguments for each digshape command. + + --todo: make this read only so we can reset to defaults. Dunno. Maybe https://www.lua.org/pil/13.4.5.html? maybe there's a idiomatic way. + --requireOrigin[bool]: do we need the origin to be set? --requireMajor[bool]: " controlPointA (major) set? --requireZ[bool]: " cursor/view on same z as origin/controlPoints? @@ -189,26 +282,28 @@ DigshapeUI.ATTRS { -- -- -- --inc=[numeric]: what is the default +- amount to change this value by when adjusted by GUI. If values={}, increment must be int,and is the number of indicies to advance(% len). TODO: SHIFT-inc: *2, CTRL-inc: *5, ALT-inc: /10; modifiers stack. --]] - - current = { command = "circle", args = {} }, --current stores the currently active command, and it's arguments and values. - --COMMAND={requireOrigin = false, requireMajor = false, requireZ = false, allowFilled = false, args = {name="",required=true,default=nil,type="",desc=""}, runSilent = true, digMode = nil, desc = "Set the origin"}, origin = { requireOrigin = false, requireMajor = false, requireZ = false, allowFilled = false, args = nil, runSilent = true, digMode = nil, desc = "Set the origin" }, controla = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = true, args = nil, runSilent = false, digMode = "@", desc = "Set the first control point (A, or 'major')" }, swap = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = true, args = nil, runSilent = false, digMode = "@", desc = "Swap the origin and cursor" }, - circle = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = true, args = nil, runSilent = false, digMode = "@", desc = "Draw a circle" }, + allCommands = { requireOrigin = true, requireMajor = false, requireZ = false, allowFilled = false, args = { "command", default = "circle", values = { "circle", "line", "curve", "ellipse", "star", "polygon", "spiral" }, commandBase = { "circle", "line", "curve", "ellipse", "star", "polygon", "spiral" }, type = "string", runSilent = false, digMode = "@", desc = "Command selection" }, }, - line = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = false, args = nil, runSilent = false, digMode = "@", desc = "Draw a line" }, + circle = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = true, args = nil, runSilent = false, digMode = "@", desc = "Draw a circle", aliases = { "c", "c2", "circle2p" } }, - ellipse = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = true, args = {}, runSilent = false, digMode = "@", desc = "Draw a ellipse" }, + line = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = false, args = nil, runSilent = false, digMode = "@", desc = "Draw a line", aliases = { "l", "ray" } }, - polygon = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = false, args = { { name = "sides", required = true, default = 3, type = "int", desc = "Number of sides of the polygon" }, { name = "vertex", required = true, default = false, type = "bool", desc = "Is the cursor on a vertex, or the midpoint of a side?" }, }, runSilent = false, digMode = "@", desc = "Draw a polygon" }, + ellipse = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = false, args = { { name = "mode", required = true, default = { default = "bbox", values = { "bbox", "2axis" }, commandBase = { "ellipse", "ellipse3p" }, inc = 1 }, type = "string", desc = "Method of layout", guiOnlyArg = true } }, runSilent = false, digMode = "@", desc = "Draw a ellipse", aliases = { "e" } }, --default.commandBase is used to call different commands when the mode argument is cycled. Used in runCurrentCommand() - star = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = false, args = { { name = "points", required = true, default = 5, type = "int", desc = "Number of points of the star" }, { name = "skip", required = true, default = 2, type = "int", desc = "How many to skip when connecting...?" }, }, runSilent = false, digMode = "@", desc = "Draw a star" }, + ellipse3p = { requireOrigin = true, requireMajor = true, requireZ = true, allowFilled = false, args = { { name = "mode", required = true, default = { default = "2axis", values = { "bbox", "2axis" }, commandBase = { "ellipse", "ellipse3p" }, inc = 1 }, type = "string", desc = "Method of layout", guiOnlyArg = true } }, runSilent = false, digMode = "@", desc = "Draw a ellipse", aliases = { "e3" } }, --crude hack to allow ellipse and ellipse3p to switch named on the arg. + + polygon = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = false, args = { { name = "sides", required = true, default = 3, type = "int", desc = "Number of sides of the polygon" }, { name = "vertex", required = true, default = false, type = "bool", desc = "Is the cursor on a vertex, or the midpoint of a side?" }, }, runSilent = false, digMode = "@", desc = "Draw a polygon", aliases = { "p", "poly", "ngon" } }, + + star = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = false, args = { { name = "points", required = true, default = 5, type = "int", desc = "Number of points of the star" }, { name = "skip", required = true, default = 2, type = "int", desc = "How many to skip when connecting...?" }, }, runSilent = false, digMode = "@", desc = "Draw a star", aliases = { "s", "st" } }, + + spiral = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = false, args = { { name = "coils", required = true, default = 2, type = "int", desc = "Number of turns the spiral makes." }, { name = "skip", required = true, default = 1, type = "int", desc = "Draw every # points along spiral." }, { name = "rotate", required = true, default = { default = 0, mod = 360, inc = 15, type = "int" }, type = "int", desc = "Rotate the spiral, 0-360." }, }, runSilent = false, digMode = "@", desc = "Draw a spiral", aliases = { "sp", "coil" } }, - spiral = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = false, args = { { name = "coils", required = true, default = 2, type = "int", desc = "Number of turns the spiral makes." }, { name = "skip", required = true, default = 1, type = "int", desc = "Draw every # points along spiral." }, { name = "rotate", required = true, default = { default = 0, min = -360, max = 360, inc = 15, type = "int" }, type = "int", desc = "Rotate the spiral, 0-360." }, }, runSilent = false, digMode = "@", desc = "Draw a spiral" }, flood = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = true, args = { { name = "max", required = false, default = 10000, type = "int", desc = "Maximum number of tiles filled before aborting. Larger numbers just take longer to complete." }, { name = "diagonals", required = false, default = false, type = "bool", desc = "Should the flood escape through corners?" }, }, runSilent = false, digMode = "@", desc = "Floodfill current designation" }, @@ -217,7 +312,7 @@ DigshapeUI.ATTRS { curve = { requireOrigin = true, requireMajor = true, requireZ = true, allowFilled = false, args = { { name = "Sharpness", required = true, default = { default = 1.5, min = 0, max = 100, inc = 0.1, type = "float" }, type = "float", desc = "How strongly the curve is pulled towards the cursor" } }, runSilent = false, digMode = "@", desc = "Draw a curve (bezier) from origin to major pulled towards cursor" }, --todo: allow filled. Also draw line, then fill shape --{ default = 1.5, min = 0, max = 100, inc = 0.1, type = "float" } --arc = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = true, args = nil, runSilent = false,digMode="@",desc="Draw an arc from origin to major passing through cursor." }, - downstair = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = true, args = { { name = "depth", required = true, default = 1, type = "int", desc = "Number of z levels down to designate." }, { name = "start", required = true, default = true, type = "bool", desc = "Should the starting level be updown [false] or down [true]" }, }, runSilent = false, digMode = "@", desc = "Designate a 3x3 block of updown stairs, corners and center only" }, + downstair = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = false, args = { { name = "depth", required = true, default = 10, type = "int", desc = "Number of z levels down to designate." }, { name = "start", required = true, default = true, type = "bool", desc = "Should the starting level be updown [false] or down [true]" }, }, runSilent = false, digMode = "@", desc = "Designate a 3x3 block of updown stairs, corners and center only" }, }, @@ -231,6 +326,7 @@ DigshapeUI.ATTRS { } function DigshapeUI:init() + stdout("CAL", debug.getinfo(1, 'n').name or "@ line " .. debug.getinfo(1, 'S').linedefined) self.saved_mode = df.global.ui.main.mode df.global.ui.main.mode = df.ui_sidebar_mode.LookAround @@ -252,7 +348,7 @@ function DigshapeUI:init() on_activate = self:callback('buttonCallback_swapOrigin') }, NEWLINE, - { key = "CUSTOM_A", text = "Set major", key_sep = ": ", + { key = "CUSTOM_A", text = "Set major", key_sep = ": ", id = "button_setCtrlA", on_activate = self:callback('buttonCallback_setControlA') }, NEWLINE, @@ -270,7 +366,9 @@ function DigshapeUI:init() { key = "CUSTOM_P", text = "Set digshape command", key_sep = ": ", on_activate = self:callback('buttonCallback_setCommand'), }, NEWLINE, - { text = "[ digshape command ]", gap = 2, pen = COLOR_YELLOW, id = "label_digshapeCommand" }, NEWLINE, + {text="[ ", pen = COLOR_YELLOW, gap = 2}, + { text = "[ digshape command ]", pen = COLOR_YELLOW, id = "label_digshapeCommand" }, + {text=" ]", pen = COLOR_YELLOW},NEWLINE, NEWLINE, @@ -323,10 +421,10 @@ function DigshapeUI:init() NEWLINE, ---[[ { key = "CUSTOM_SHIFT_P", text = "Reset arguments", key_sep = ": ", id = "button_resetArgs", - on_activate = self:callback('buttonCallback_argAdjust', -1, "reset") - --self:callback('buttonCallback_setCommand')("Q1") - }, NEWLINE,]] + --[[ { key = "CUSTOM_SHIFT_P", text = "Reset arguments", key_sep = ": ", id = "button_resetArgs", + on_activate = self:callback('buttonCallback_argAdjust', -1, "reset") + --self:callback('buttonCallback_setCommand')("Q1") + }, NEWLINE,]] } }, @@ -385,9 +483,14 @@ function DigshapeUI:init() frame = { b = 1, l = 1 }, --place it inset one tile off the bottom left view_id = "bottomMenu", text = { + --{ key = "HELP", text = "Help for cur. cmd.", key_sep = ": ", + -- on_activate = self:callback('buttonCallback_showHelpPopup'), + --}, NEWLINE, + { key = "STRING_A092", text = "Move view to see origin", key_sep = ": ", on_activate = self:callback('buttonCallback_recenterView'), }, NEWLINE, + { key = "CUSTOM_Z", text = "Undo last digshape", key_sep = ": ", on_activate = self:callback('buttonCallback_undo'), }, NEWLINE, @@ -404,26 +507,29 @@ function DigshapeUI:init() } --if origin isn't set, just put it at the cursor - --todo: make this a skippable option based on argument. Other values might include "here", "last", "coords" + --todo: make this a skippable option named on argument. Other values might include "here", "last", "coords" if self.origin == nil then self:runDigshapeCommand("digshape lua origin") end - --assume we're always working on the current z level, todo: make this an option based on argument + --assume we're always working on the current z level, todo: make this an option named on argument self.autosetZtoCurrent = true --display the current command preview - self:buttonCallback_setDig(self.activeDesgination) - self:setCommand(self.currentCommand) - self:previewCurrentCommand() + --self:buttonCallback_setDig(self.activeDesgination) + self:setCommand(self.activeCommand.name) + --self:updateMenuDisplay() + --self:previewCurrentCommand() --dfhack.gui.revealInDwarfmodeMap(self.origin) end function DigshapeUI:onDestroy() + stdout("CAL", debug.getinfo(1, 'n').name or "@ line " .. debug.getinfo(1, 'S').linedefined) df.global.ui.main.mode = self.saved_mode end function DigshapeUI:toggleSubViewVis(viewID, setActiveByVis) + stdout("CAL", debug.getinfo(1, 'n').name or "@ line " .. debug.getinfo(1, 'S').linedefined) --toggles visibility (and by default, active status) of a id'd subview. if self.subviews == nil then stdout("ERR", "View element not found:", viewID) @@ -446,6 +552,7 @@ function DigshapeUI:toggleSubViewVis(viewID, setActiveByVis) end function DigshapeUI:toggleSubViewActive(viewID) + stdout("CAL", debug.getinfo(1, 'n').name or "@ line " .. debug.getinfo(1, 'S').linedefined) --toggles View.Active, which can change the display, and stops it from getting keypresses. if self.subviews == nil then stdout("ERR", "View element not found:", viewID) @@ -477,84 +584,360 @@ local lastZ = df.global.cursor.z --end -function DigshapeUI:parseCommand() - stdout("MSG", "parsecommand:", self.currentCommand) - --if self.parsedCommand == nil then - local commandBase = self.currentCommand:lower():match("^%a+") - self.parsedCommand = commandBase - self.digshapeCommands.current = { command = commandBase, args = {} } - - local args = { commandargs = self.digshapeCommands[commandBase].args, --copyall() the command specific arguments (eg "chords" for digshape spiral). We use copyall to get a copy so it remains unchanged. - genericargs = { - fill = "NA", --NA if unsupported by this command, "filled" or "hollow" if digshape supports it for this command - digmode = "@", --'@': replace with current digmode. - mode = "designating"--"designating" or "marking" or "toggling" - } } - if self.digshapeCommands[commandBase].allowFilled then - args.genericargs.fill = self.designateFilled and "filled" or "hollow" - end - -- if self.digshapeCommands[commandBase].digMode ~= nil then - args.genericargs.digmode = self.digshapeCommands[commandBase].digMode - -- end - --print("1>") for k,v in pairs(args.commandargs) do print(" 1>"..k..":"..v) end for k,v in pairs(args.genericargs) do print(" 2>"..k..":"..v) end - - - --local buildCommand = { - -- --for each digshape command, setup it's arg string, and TODO: check that it's conditions are met. - -- circle = function(self, args) - -- local temp = self.digshapeCommands[self.digshapeCommands.current.command].allowFilled and self.designateFilled and "filled" or "hollow" - -- args.genericargs.fill = temp - -- end, - -- origin = function(self, args) - -- end, - --} - --buildCommand[commandBase](self, args) - --print("2>") for k,v in pairs(args.commandargs) do print(" 1>"..k..":"..v) end for k,v in pairs(args.genericargs) do print(" 2>"..k..":"..v) end - if args.commandargs ~= nil then - --self.digshapeCommands[self.digshapeCommands.current.command].args ~= nil then - --print(">", self.digshapeCommands.current.command) - for argi = 1, #args.commandargs do - --for k, _ in pairs(self.digshapeCommands[self.digshapeCommands.current.command].args) do - -- print("->", argi) - if args.commandargs[argi].currentValue == nil then - args.commandargs[argi].currentValue = args.commandargs[argi].default + +function DigshapeUI:updateMenuDisplay() + stdout("CAL", debug.getinfo(1, 'n').name or "@ line " .. debug.getinfo(1, 'S').linedefined) + -- update all arg display and value, plus major/fill;;or update given arg to current value. TODO + local command = self.activeCommand.name + --if command ~=self.activeCommand.name then + -- print(command, self.activeCommand.name, self.activeCommand.digshapeString) + -- --stdout("WRN", "self.activeCommand.name != self.activeCommand.name",self.activeCommand.name ,self.activeCommand.name) + -- -- printtable(self.activeCommand.name) + -- -- worms() + --end + + local showArgs + showArgs = function(self, command) + stdout("CAL", debug.getinfo(1, 'n').name or "@ line " .. debug.getinfo(1, 'S').linedefined) + command = command or self.activeCommand.name + -- stdout("MSG", ">showargs>" .. command.."<") + -- print(self.activeCommand.name) + if self.digshapeCommands[command] == nil then + stdout("WRN", "No args for:" .. command) + return nil + end + + local args = self.digshapeCommands[command].args + local ids = { "button_arg#dec", "button_arg#inc", "label_arg#name", "label_arg#sepA", "label_arg#sepB", "label_arg#sepC", "label_arg#value" }--, "button_resetArgs" }--,"label_arg#desc"} + + local nargs = 0 + if args ~= nil then + for i, v in ipairs(args) do + for k = 1, #ids do + local index = string.gsub(ids[k], "#", i) + -- print("vis", i, k, index) + self:updateMenuArg("digshapeMenu", index, { disabled = false }) + + end + printtable(v, "ARG " .. i) + self:updateMenuArg("digshapeMenu", "label_arg" .. i .. "name", { text = v.name }) + self:updateMenuArg("digshapeMenu", "label_arg" .. i .. "value", { text = tostring(v.currentValue) }) + nargs = nargs + 1 end - if type(args.commandargs[argi].currentValue) == "table" then - args.commandargs[argi].currentValue = args.commandargs[argi].default.default - --todo: delete this once all the commands have full default={} stuff. + end + + if nargs < 3 then + for i = nargs + 1, 3 do + for k = 1, #ids do + local index = string.gsub(ids[k], "#", i) + -- print("hide", i, k, index) + self:updateMenuArg("digshapeMenu", string.gsub(ids[k], "#", i), { dpen = self.pens.disabledMenu, disabled = true }) + end + + self:updateMenuArg("digshapeMenu", "label_arg" .. i .. "name", { text = "--------" }) + self:updateMenuArg("digshapeMenu", "label_arg" .. i .. "value", { text = "-" }) end - self.subviews.digshapeMenu.text_ids["label_arg" .. argi .. "value"].text = tostring(args.commandargs[argi].currentValue) - self.subviews.digshapeMenu.text_ids["label_arg" .. argi .. "name"].text = args.commandargs[argi].name - self.parsedCommand = self.parsedCommand .. " " .. tostring(args.commandargs[argi].currentValue) end end - if args.genericargs.fill ~= "NA" then - self.parsedCommand = self.parsedCommand .. " " .. args.genericargs.fill + + + --update digshape command display + self:rebuildDigshapeArgumentString() + self:updateMenuArg("digshapeMenu", "label_digshapeCommand", { text = self.activeCommand.digshapeString }) + + --update control point display/lockout + showArgs(self) + local fillToggle = self:getCurrentFill() + self:updateMenuArg("controlPointsMenu", "button_toggleFill", { text = "Toggle fill: " .. fillToggle, disabled = (fillToggle == "NA"), dpen = self.pens.disabledMenu }) + + --update setmajor display + --printtable(self.digshapeCommands[command]) + if self.digshapeCommands[command].requireMajor then + if self.major == nil then + print("enable alert major") + self:updateMenuArg("controlPointsMenu", "button_setCtrlA", { dpen = self.pens.alertMenu, disabled = false }) + end + print("enable major") + self:updateMenuArg("controlPointsMenu", "button_setCtrlA", { dpen = self.pens.enabledMenu, disabled = false }) + else + --print("disable major") + self:updateMenuArg("controlPointsMenu", "button_setCtrlA", { dpen = self.pens.disabledMenu, disabled = false }) + --don't actually disable the key, just darken it to indicate we don't need it + end + + --update argument display + --print(self.activeCommand.name) + --printtable(self.activeCommand.name) + -- + --printtable(self.digshapeCommands[self.activeCommand.name]) + --printtable(self.digshapeCommands[self.activeCommand.name].args) + --[self.activeCommand.name] + if self.activeCommand.args ~= nil and self.activeCommand.args ~= {} then + showArgs(self, command)--showargs first so that preview updates their values. + else + printtable(self.activeCommand) + stdout("WRN", "Can't update args in menu, no args set.") + end + + --update designation display + + +end + +--self:updateMenuArg("","",{}) +function DigshapeUI:updateMenuArg(menu, textID, newvalues) + stdout("CAL", debug.getinfo(1, 'n').name or "@ line " .. debug.getinfo(1, 'S').linedefined) + local temp = self.subviews[menu].text_ids[textID] + + if type(newvalues) == "table" then + if newvalues.dpen ~= nil then + temp.dpen = newvalues.dpen + end + if newvalues.disabled ~= nil then + temp.disabled = newvalues.disabled + end + if newvalues.text ~= nil then + temp.text = newvalues.text + end end - if args.genericargs.digmode ~= nil then - self.parsedCommand = self.parsedCommand .. " " .. args.genericargs.digmode:gsub("@", self.activeDesgination) +end +function DigshapeUI:changeCommandTo(newCommand) + --this function is what the input prompt returns the input with. We will detect this value becoming not nil at the top of onRenderBody() and call setCommand(self.changeCommandToCommand). + self.changeCommandToCommand = newCommand +end + +function DigshapeUI:setCommand(newCommand) + stdout("CAL", debug.getinfo(1, 'n').name or "@ line " .. debug.getinfo(1, 'S').linedefined) + stdout(">>>", "Setcommand ---------------------") + stdout("MSG", "Set command=", newCommand) + local commandBase = "" + for k, v in pairs(self.digshapeCommands) do + if newCommand == k then + commandBase = k + break + elseif v.aliases ~= nil then + for i = 1, #v.aliases do + if newCommand == v.aliases[i] then + commandBase = k + break + end + end + + end end + newCommand = commandBase + + self.activeCommand.name = newCommand + self.activeCommand.digshapeString = nil + local command = self:parseCommand() + self:runDigshapeCommand("digshape lua status") - self.digshapeCommands.current.args = args + --this is the one exception to calling preview outside of onInput, as onInput has already resolved at this point. TODO: simulate input to force redraw + self:updateMenuDisplay() + self:previewCurrentCommand() +end + +function DigshapeUI:rebuildDigshapeArgumentString() + stdout("CAL", debug.getinfo(1, 'n').name or "@ line " .. debug.getinfo(1, 'S').linedefined) + local newstring="" + local words = {} + while true do + --print(self.activeCommand.digshapeString) + local w=string.gsub(self.activeCommand.digshapeString, "^(%a*)( ?.-)$","%1" )--:sub(1) + if w=="" then + -- print("done") + break + end + -- print("W='"..w.."'") + table.insert(words, w) + self.activeCommand.digshapeString=self.activeCommand.digshapeString:gsub(w,"") + end + self.activeCommand.digshapeString="" + printtable(words) + newstring=words[1] + words[1]=nil + + for index,w in ipairs(words) do + print(index, w) + if w == "digshape" or w == "lua" or w == "preview" then + newstring=newstring.." "..w + end + for k,v in pairs(self.digshapeCommands) do + if w==k then + newstring=newstring.." "..w:tostring() + end + end + end + -- print(newstring) + if self.activeCommand.args ~= nil then + for k,v in pairs(self.activeCommand.args) do + -- print(k) + -- printtable(v) + if v.guiOnlyArg==nil then + newstring=newstring.." "..tostring(v.currentValue) + end + end + + end + -- printtable(self.activeCommand) + newstring=newstring.." "..self.activeCommand.digshapeArgs.digmode + self.activeCommand.digshapeString=newstring + --print("NS='"..newstring.."'") + -- = self.activeCommand.digshapeString:gsub("^((digshape )?(lua )?(preview )?(%a+)(.-)$","%1%2%3%4") + + -- worms() + +end + +function DigshapeUI:clearCommand() + stdout("CAL", debug.getinfo(1, 'n').name or "@ line " .. debug.getinfo(1, 'S').linedefined) + stdout(">>>", "Clear Command") + + local currentFill = self.activeCommand.digshapeArgs.fill -- save this so it carries between commands + local commandBase = self.activeCommand.name--:lower():match("^%a+") + self.activeCommand = self.resetActiveCommand() + self.activeCommand = { name = commandBase, args = self.digshapeCommands[commandBase].args, digshapeString = commandBase, digshapeArgs = { + fill = "NA", --NA if unsupported by this command, "filled" or "hollow" if digshape supports it for this command + digmode = "@", --'@': replace with current digmode. + mode = "designating"--"designating" or "marking" or "toggling" + } } + --printtable(self.activeCommand) + --Make the self.activeCommand.digshapeArgs correct + if self.activeCommand.allowFilled then + if currentFill ~= "NA" then + self.activeCommand.digshapeArgs.fill = "hollow" + else + self.activeCommand.digshapeArgs.fill = currentFill + end + else + self.activeCommand.digshapeArgs.fill = "NA" + end + + self.activeCommand.digshapeArgs.digmode = self.digshapeCommands[commandBase].digMode + + +end + +function DigshapeUI:parseCommand() + --change a self.activeCommand.name into a full self.activeCommand, with complete digshapeString (not inc "digshape lua preview?") + stdout("CAL", debug.getinfo(1, 'n').name or "@ line " .. debug.getinfo(1, 'S').linedefined) + stdout("MSG", "parsecommand:", self.activeCommand.name) + + + --if self.activeCommand.digshapeString == nil then + --print("ac.b=", type(self.activeCommand.name)) + --printtable(self.activeCommand.name) + + --reset and clear out self.activeCommand + self:clearCommand() + printtable(self.activeCommand, "post clear") + + --Setup the command arguments (to defaults), and build the digshapeString + if self.activeCommand.args ~= nil then + for argi = 1, #self.activeCommand.args do + --print("-> ARG", argi) + --printtable(self.activeCommand.args[argi],"ARG "..argi) + if self.activeCommand.args[argi].currentValue == nil then + self.activeCommand.args[argi].currentValue = self.activeCommand.args[argi].default + + if type(self.activeCommand.args[argi].currentValue) == "table" then + self.activeCommand.args[argi].currentValue = self.activeCommand.args[argi].default.default + --todo: delete this once all the commands have full default={} stuff. + end + + end + + --printtable(self.activeCommand) + if self.activeCommand.args[argi].guiOnlyArg ~= true then + self.activeCommand.digshapeString = self.activeCommand.digshapeString .. " " .. tostring(self.activeCommand.args[argi].currentValue) + else + -- print("don't add this arg(" .. self.activeCommand.name .. ":" .. self.activeCommand.args[argi].name .. ") to digshape, internal use only.") + + end + end + end + --print("fill", self.activeCommand.digshapeArgs.fill) + if self.activeCommand.digshapeArgs.fill ~= "NA" then + self.activeCommand.digshapeString = self.activeCommand.digshapeString .. " " .. self.activeCommand.digshapeArgs.fill + end + --print("dig", self.activeCommand.digshapeArgs.digmode) + if self.activeCommand.digshapeArgs.digmode ~= nil then + self.activeCommand.digshapeString = self.activeCommand.digshapeString .. " " .. self.activeCommand.digshapeArgs.digmode:gsub("@", self.activeDesgination) + end + + --self.activeCommand = self.activeCommand -- end + self:rebuildDigshapeArgumentString() - self.subviews.digshapeMenu.text_ids.label_digshapeCommand.text = "[ " .. self.parsedCommand .. " ]" + --self.subviews.digshapeMenu.text_ids.label_digshapeCommand.text = "[ " .. self.activeCommand.digshapeString .. " ]"--update menu display + stdout("MSG", "Parsed command:", self.activeCommand.digshapeString) + --return self.activeCommand.name + printtable(self.activeCommand, "done") - return commandBase end function DigshapeUI:runCurrentCommand(preview) + stdout("CAL", debug.getinfo(1, 'n').name or "@ line " .. debug.getinfo(1, 'S').linedefined) + -- print(">>>>", self.activeCommand.name, self.activeCommand.digshapeString) local command = ("digshape lua %s"):format(preview and "preview" or "") - local baseCommand = self:parseCommand() - command = command .. " " .. self.parsedCommand + + --Check for a BASECOMMAND argument, in case this is a command that needs to be run as another command. (eg. GUI ellipse runs both "ellipse bbox" and "ellipse 2axis") + local nameCommand = self.activeCommand.name--self:parseCommand() + local oldCommand = nil --if we swap commands during exicution, save here to revert. + + if self.digshapeCommands[nameCommand].args ~= nil then + --check all args to see if one has a defaults table including 'commandBase'. + --if it does, and nameCommand(currently trying to run) does not match commandBase(what this option wants to be run as), then we need to switch command to it. + --printtable(self.digshapeCommands[nameCommand],i) + local thisCommandIndex = nil + local matchCommandIndex = nil + printtable(self.digshapeCommands[nameCommand]) + for i = 1, #self.digshapeCommands[nameCommand].args do + if type(self.digshapeCommands[nameCommand].args[i].default) == "table" and self.digshapeCommands[nameCommand].args[i].default.commandBase ~= nil then + --get index of this arg in it's values list, and use that to select the right commandBase + local swaptocommand = "" + local valueToMatch = self.activeCommand.args[i].currentValue--self.digshapeCommands[nameCommand].args[i].currentValue + for ii, vv in ipairs(self.digshapeCommands[nameCommand].args[i].default.values) do + -- print("checkmatch:", self.activeCommand.digshapeString, ii, vv,valueToMatch) + --printtable(self.digshapeCommands[nameCommand].args,i) + + if self.digshapeCommands[nameCommand].args[i].default.commandBase[ii] == self.activeCommand.name then + thisCommandIndex = ii + end + + if valueToMatch == vv then + --print("matchfound", vv, ii) + matchCommandIndex = ii + end + end + + if thisCommandIndex ~= matchCommandIndex then + swaptocommand = self.digshapeCommands[nameCommand].args[i].default.commandBase[matchCommandIndex] + stdout(">>>", "Command swap: ", self.activeCommand.name, swaptocommand, thisCommandIndex, matchCommandIndex) + oldCommand = self.activeCommand + self:clearCommand() + self:setCommand(swaptocommand) + break + end + end + end + end + + printtable(self.activeCommand) + self:rebuildDigshapeArgumentString() + command = command .. " " .. self.activeCommand.digshapeString stdout("CMD", command, preview) + if not verbose and not preview then + local out, _ = command:gsub("lua ", "") + stdout("OUT", out) + end --check to make sure digshape will like the command, if not, don't bother calling and just return. - local commandTests = self.digshapeCommands[baseCommand] - if baseCommand == nil then + local commandTests = self.digshapeCommands[nameCommand] + if nameCommand == nil then stdout("WRN", "Current command will not exicute, skipping. 1") return end @@ -579,13 +962,18 @@ function DigshapeUI:runCurrentCommand(preview) end self:runDigshapeCommand(command) + if nameCommand ~= origCommand then + --print("now reset to", origCommand) + --self.activeCommand = oldCommand + end end function DigshapeUI:runDigshapeCommand(command) + stdout("CAL", debug.getinfo(1, 'n').name or "@ line " .. debug.getinfo(1, 'S').linedefined) --simple validity checks - stdout("CMD", "RUN:", command) + stdout("CMD", "RUNNING: '" .. command .. "'") if command == nil then - print("nil command") + stdout("ERR","nil command") return nil end @@ -645,13 +1033,17 @@ function DigshapeUI:runDigshapeCommand(command) self.major.z = currentz end end + + stdout(">>>", "<<<<<<<< End call") end function DigshapeUI:previewCurrentCommand() + stdout("CAL", debug.getinfo(1, 'n').name or "@ line " .. debug.getinfo(1, 'S').linedefined) self.runCurrentCommand(self, true) end function DigshapeUI:commitCurrentCommand() + stdout("CAL", debug.getinfo(1, 'n').name or "@ line " .. debug.getinfo(1, 'S').linedefined) self.runCurrentCommand(self, false) end @@ -666,6 +1058,7 @@ local function paintMapTile(dc, vp, cursor, pos, ...) end function DigshapeUI:buttonCallback_setOrigin() + stdout("CAL", debug.getinfo(1, 'n').name or "@ line " .. debug.getinfo(1, 'S').linedefined) stdout("MSG", ">setorigin>") self:runDigshapeCommand("digshape lua origin") @@ -673,6 +1066,7 @@ function DigshapeUI:buttonCallback_setOrigin() end function DigshapeUI:buttonCallback_swapOrigin() + stdout("CAL", debug.getinfo(1, 'n').name or "@ line " .. debug.getinfo(1, 'S').linedefined) self:runDigshapeCommand("digshape lua swap") self:previewCurrentCommand() @@ -680,76 +1074,33 @@ function DigshapeUI:buttonCallback_swapOrigin() end function DigshapeUI:buttonCallback_setControlA() + stdout("CAL", debug.getinfo(1, 'n').name or "@ line " .. debug.getinfo(1, 'S').linedefined) self:runDigshapeCommand("digshape lua major") self:previewCurrentCommand() end function DigshapeUI:buttonCallback_toggleFilled() - self.designateFilled = not self.designateFilled - self.parsedCommand = nil + stdout("CAL", debug.getinfo(1, 'n').name or "@ line " .. debug.getinfo(1, 'S').linedefined) + self.activeCommand.digshapeArgs.fill = not self.activeCommand.digshapeArgs.fill + self.activeCommand.digshapeString = nil self:previewCurrentCommand() - self.subviews.controlPointsMenu.text_ids.button_toggleFill.text = "Toggle fill: " .. self:getCurrentFill() -end + local newvalue = self:getCurrentFill() -function DigshapeUI:setCommand(newCommand) - local showArgs - showArgs = function(self, command) - -- stdout("MSG", ">showargs> " .. command) - -- print(self.currentCommand) - local args = self.digshapeCommands[command].args - local ids = { "button_arg#dec", "button_arg#inc", "label_arg#name", "label_arg#sepA", "label_arg#sepB", "label_arg#sepC", "label_arg#value"}--, "button_resetArgs" }--,"label_arg#desc"} - - local nargs = 0 - if args ~= nil then - for i, v in ipairs(args) do - for k = 1, #ids do - local index = string.gsub(ids[k], "#", i) - -- print("vis", i, k, index) - local temp = self.subviews.digshapeMenu.text_ids[index] - temp.disabled = false - end - nargs = nargs + 1 - end - end - - if nargs < 3 then - for i = nargs + 1, 3 do - for k = 1, #ids do - local index = string.gsub(ids[k], "#", i) - -- print("hide", i, k, index) - local temp = self.subviews.digshapeMenu.text_ids[string.gsub(ids[k], "#", i)] - temp.dpen = CLEAR_PEN - temp.disabled = true - end - self.subviews.digshapeMenu.text_ids["label_arg" .. i .. "name"].text = "--------" - self.subviews.digshapeMenu.text_ids["label_arg" .. i .. "value"].text = "-" - end - - end - end - self.currentCommand = newCommand - self.parsedCommand = nil - local command = self:parseCommand() - self:runDigshapeCommand("digshape lua status") - showArgs(self, command)--showargs first so that preview updates their values. - self:previewCurrentCommand() end function DigshapeUI:buttonCallback_setCommand() + stdout("CAL", debug.getinfo(1, 'n').name or "@ line " .. debug.getinfo(1, 'S').linedefined) --TODO: can maybe fix the stupid transparent edit box by making own class that supers all except changes the root gui:framedScreen.frame_background pen to not CLEAR_PEN.... or maybe editfield.on_char or on_change - - dialog.showInputPrompt("Set digshape command", "Enter a digshape command", COLOR_WHITE, "", function(result) - self:setCommand(result) + self:changeCommandTo(result) end ) - - end function DigshapeUI:buttonCallback_setDig(mode) + stdout("CAL", debug.getinfo(1, 'n').name or "@ line " .. debug.getinfo(1, 'S').linedefined) local buttonCallback_setDig_labelhelper buttonCallback_setDig_labelhelper = function(mode, set) set = set == "set" and "set" or "clear" @@ -801,20 +1152,20 @@ function DigshapeUI:buttonCallback_setDig(mode) --do the update: self.activeDesgination = mode - self.parsedCommand = nil --regen digshape command + self.activeCommand.digshapeString = nil --regen digshape command self:parseCommand() self:previewCurrentCommand() end function DigshapeUI:getCurrentFill() + stdout("CAL", debug.getinfo(1, 'n').name or "@ line " .. debug.getinfo(1, 'S').linedefined) local value = "" - if self.digshapeCommands[self.digshapeCommands.current.command].allowFilled == false then + if self.digshapeCommands[self.activeCommand.name].allowFilled == false then value = "NA" - elseif self.designateFilled then - value = "Filled" else - value = "Hollow" + value = self.activeCommand.digshapeArgs.fill + end return value end @@ -822,17 +1173,23 @@ end function DigshapeUI:buttonCallback_argAdjust(argNum, argDir, argMod) --TODO: make these curried as ARG#, INC/DEC={"+", "-"}, MODIFIER?S? -- --(TODO: argMod:: SHIFT-inc: *2, CTRL-inc: *5, ALT-inc: /10; modifiers stack. ) - + stdout("CAL", debug.getinfo(1, 'n').name or "@ line " .. debug.getinfo(1, 'S').linedefined) local tempDir = 1 if argDir == "-" then tempDir = tempDir * -1 end local tempMod = 1 - local arg = self.digshapeCommands.current.args.commandargs[argNum] + local arg = self.activeCommand.args[argNum] + stdout(">>>", "argadj---------------------", argNum, argDir, argMod) + printtable(self.activeCommand, "Arg Adj") + if arg == nil then + stdout("ERR","NIL ARG", argNum, argDir, argMod) + return + end if argDir == "reset" then - stdout("MSG","Reset all args to default.") - self:setCommand(self.digshapeCommands.current.command) + stdout("MSG", "Reset all args to default.") + self:setCommand(self.activeCommand.name) return end @@ -841,21 +1198,45 @@ function DigshapeUI:buttonCallback_argAdjust(argNum, argDir, argMod) end tempMod = tempMod * (argMod) - stdout("argAdj: ", argNum, ") ", tempDir, tempMod) + stdout("MSG", "argAdj: " .. argNum .. ":: " .. tempDir .. "," .. tempMod) + local newvalue if type(arg.currentValue) == "boolean" then - arg.currentValue = not arg.currentValue + newvalue = not arg.currentValue + elseif type(arg.default) == "table" and arg.default.values ~= nil then + local index = 1 + local found = false + while not found do + for currentindex = 1, #arg.default.values do + if arg.default.values[currentindex] == arg.currentValue then + index = currentindex + found = true + break + end + --the while loop lets us loop around the back of the list. TODO: fix this dirty hack. + end + index = index + 1 + if index > #arg.default.values then + index = 1 + end + newvalue = arg.default.values[index] + end else - local newval = self.digshapeCommands.current.args.commandargs[argNum].currentValue + (1 * tempDir * tempMod) + newvalue = arg.currentValue + (1 * tempDir * tempMod) if type(arg.default) == "table" then - if newval < arg.default.min then - newval = arg.default.min - elseif newval > arg.default.max then - newval = arg.default.max + if arg.default.mod ~= nil then + newvalue = newvalue % arg.default.mod + elseif newvalue < arg.default.min then + newvalue = arg.default.min + elseif newvalue > arg.default.max then + newvalue = arg.default.max end end - self.digshapeCommands.current.args.commandargs[argNum].currentValue = newval end + arg.currentValue = newvalue + stdout("MSG", "ArgAdj:" .. arg.name .. "=" .. tostring(arg.currentValue)) + printtable(self.activeCommand, "Arg Adj2") + self:updateMenuDisplay() self:previewCurrentCommand() end @@ -865,10 +1246,12 @@ end --end function DigshapeUI:buttonCallback_undo() + stdout("CAL", debug.getinfo(1, 'n').name or "@ line " .. debug.getinfo(1, 'S').linedefined) dfhack.run_command("digshape undo") end function DigshapeUI:buttonCallback_commit() + stdout("CAL", debug.getinfo(1, 'n').name or "@ line " .. debug.getinfo(1, 'S').linedefined) self:commitCurrentCommand() end @@ -879,12 +1262,16 @@ end function DigshapeUI:buttonCallback_recenterView() + stdout("CAL", debug.getinfo(1, 'n').name or "@ line " .. debug.getinfo(1, 'S').linedefined) stdout("MSG", "recenter view:", self.origin) dfhack.gui.revealInDwarfmodeMap(self.origin) + --todo: move cursor back on screen too... in it's relative position? +end + +function DigshapeUI:buttonCallback_showHelpPopup() + stdout("CAL", debug.getinfo(1, 'n').name or "@ line " .. debug.getinfo(1, 'S').linedefined) + end ---function DigshapeUI:buttonCallback_() --- ---end -- --function DigshapeUI:buttonCallback_() -- @@ -909,6 +1296,7 @@ end function DigshapeUI:renderOverlay() + --stdout("CAL",debug.getinfo(1,'n').name or "@ line "..debug.getinfo(1,'S').linedefined) --todo: consider --https://docs.dfhack.org/en/stable/docs/Lua%20API.html#penarray-class for speedup local vp = self:getViewport() local dc = gui.Painter.new(self.df_layout.map) @@ -928,9 +1316,9 @@ function DigshapeUI:renderOverlay() if self.origin then paintMapTile(dc, vp, df.global.cursor, self.origin, '+', self.pens['origin']) end - if self.digshapeCommands.current.command~=nil then + if self.activeCommand.name ~= nil then if self.major then - if self.digshapeCommands[self.digshapeCommands.current.command].requireMajor then + if self.digshapeCommands[self.activeCommand.name].requireMajor then paintMapTile(dc, vp, df.global.cursor, self.major, 'a', self.pens['ctrl_A']) end @@ -942,13 +1330,23 @@ function DigshapeUI:renderOverlay() end function DigshapeUI:onRenderBody(dc) + --stdout("CAL",debug.getinfo(1,'n').name or "@ line "..debug.getinfo(1,'S').linedefined) + if self.changeCommandToCommand ~= nil then + self:setCommand(self.changeCommandToCommand) + self.changeCommandToCommand = nil + end self:renderOverlay() dc:clear():seek(1, 1):pen(COLOR_WHITE):string("Digshape - " .. self.state) + end function DigshapeUI:onInput(keys) + stdout("CAL", debug.getinfo(1, 'n').name or "@ line " .. debug.getinfo(1, 'S').linedefined) + stdout(">>>", "===========ON INPUT===========") + + --TODO: deal with multi-key presses, because keys is an array of individuals. if df.global.cursor.x == -30000 then local vp = self:getViewport() @@ -969,6 +1367,9 @@ function DigshapeUI:onInput(keys) elseif self:propagateMoveKeys(keys) then return end + stdout("CAL", debug.getinfo(1, 'n').name or "@ line " .. debug.getinfo(1, 'S').linedefined) + --self:updateMenuDisplay() + --self:previewCurrentCommand() end if not (dfhack.gui.getCurFocus():match("^dwarfmode/Default") or dfhack.gui.getCurFocus():match("^dwarfmode/Designate") or dfhack.gui.getCurFocus():match("^dwarfmode/LookAround")) then From 99bcf20b072661e6beb9c567e4ff6a2375691f2e Mon Sep 17 00:00:00 2001 From: Quatch Date: Thu, 1 Apr 2021 00:12:29 -0400 Subject: [PATCH 09/12] bugfixing reversions plus floodfill -fixed bug: not respecting designation mode -fixed bug: not accepting fill status -fixed floodfill overflowing execution. -revised floodfill digshape code to skip rechecking and redesignating same spots. --- digshape.rb | 36 +++++++- gui/digshape.lua | 228 +++++++++++++++++++++++------------------------ 2 files changed, 144 insertions(+), 120 deletions(-) diff --git a/digshape.rb b/digshape.rb index 5db2ceb..5cfacfb 100644 --- a/digshape.rb +++ b/digshape.rb @@ -366,6 +366,7 @@ def isDigPermitted(digMode, tileShape) def digAt(x, y, z, digMode = 'd', buffer: true, bufferX: $digBufferX, bufferY: $digBufferY, bufferZ: $digBufferZ, bufferD: $digBufferD) #Commit designation@coords to the map, opt save current value there to the buffer for undo. + #puts "DIGAT: "+x.to_s+","+y.to_s tile = df.map_tile_at(x, y, z) # check if the tile returned is valid, ignore if its not (out of bounds, air, etc) @@ -1101,10 +1102,27 @@ def floodfill(x,y,z,targetDig, digMode, maxCounter= 10000) #scan for next tile to dig. xStack = [x] yStack = [y] - + + checkedTiles=Hash.new #these keep us from rechecking tiles and such. + designatedTiles=Hash.new + loop do + if xStack.length <=0 then + # puts "Stack empty." + break + end + x= xw = xe = xStack.pop() y = yStack.pop() #always push/pop x&y together. + + #puts "Checking: "+x.to_s+","+y.to_s + if checkedTiles[x.to_s+","+y.to_s] !=nil then + #puts x.to_s+","+y.to_s+" already checked" + next + else + #puts x.to_s+","+y.to_s+" added to checked list" + checkedTiles[x.to_s+","+y.to_s]=true + end #search W for bounds loop do @@ -1130,7 +1148,14 @@ def floodfill(x,y,z,targetDig, digMode, maxCounter= 10000) #scan W..E filling, and checking N/S for xi in xw..xe do - digAt(xi, y, z, digMode) + #puts "scan: "+xi.to_s+","+y.to_s+" = "+designatedTiles[xi.to_s+","+y.to_s].to_s + if designatedTiles[xi.to_s+","+y.to_s] == nil then + digAt(xi, y, z, digMode) + designatedTiles[xi.to_s+","+y.to_s]=true + #puts "Dig: "+xi.to_s+","+y.to_s + else + #puts "Nodig: "+xi.to_s+","+y.to_s+" ="+designatedTiles[xi.to_s+","+y.to_s].to_s+"/" + end counter = counter -1 if counter <=0 then @@ -1139,15 +1164,18 @@ def floodfill(x,y,z,targetDig, digMode, maxCounter= 10000) undo() return end - + + + #check N/S t = df.map_tile_at(xi,y+1,z) if t && t.designation.dig == targetDig && isDigPermitted(digMode,t.shape_basic) then + #puts "push"+xi.to_s+" "+(y+1).to_s+" as row down" xStack.push(xi) yStack.push(y+1) end t = df.map_tile_at(xi,y-1,z) - if t && t.designation.dig == targetDig && isDigPermitted(digMode,t.shape_basic) then + if t && t.designation.dig == targetDig && isDigPermitted(digMode,t.shape_basic) then xStack.push(xi) yStack.push(y-1) end diff --git a/gui/digshape.lua b/gui/digshape.lua index 5ea252e..ed286b8 100644 --- a/gui/digshape.lua +++ b/gui/digshape.lua @@ -133,7 +133,7 @@ if verbose then local _, _, temp = string.find(msgtype, "^(...)$") if temp == "ERR" or temp == "MSG" or temp == "WRN" or temp == "OUT" or temp == "CMD" or temp == "RBY" or temp == ">>>" or temp == "CAL" then --output channels: MSG/WRN/ERR: information, OUT: results, RBY: digshape output passed through. - if temp == "IGNORE" or temp == "ALSOIGNORE" or temp == "RBY" then + if temp == "IGNORE" or temp == "ALSOIGNORE" or temp == "RBY5" then return --don't print end local color = { RBY = COLOR_DARKGREY, MSG = COLOR_WHITE, WRN = COLOR_YELLOW, ERR = COLOR_LIGHTRED, CMD = COLOR_LIGHTCYAN, OUT = COLOR_MAGENTA, [">>>"] = COLOR_LIGHTMAGENTA, CAL = COLOR_BROWN } @@ -235,8 +235,8 @@ DigshapeUI.ATTRS { --Designation mode variables (to be saved between commands) -- designateDigMode = {}, --what is the UI selected digMode? - designateMarking = false, --are we designating "marking" rather than standard? - designateFilled = false, --filled or hollow? (will be ignored if current command does not allow/use it) + --designateMarking = false, --are we designating "marking" rather than standard? + --designateFilled = false, --filled or hollow? (will be ignored if current command does not allow/use it) @@ -304,15 +304,14 @@ DigshapeUI.ATTRS { spiral = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = false, args = { { name = "coils", required = true, default = 2, type = "int", desc = "Number of turns the spiral makes." }, { name = "skip", required = true, default = 1, type = "int", desc = "Draw every # points along spiral." }, { name = "rotate", required = true, default = { default = 0, mod = 360, inc = 15, type = "int" }, type = "int", desc = "Rotate the spiral, 0-360." }, }, runSilent = false, digMode = "@", desc = "Draw a spiral", aliases = { "sp", "coil" } }, + flood = { requireOrigin = false, requireMajor = false, requireZ = false, allowFilled = false, args = { { name = "max", required = false, default = 10000,inc=5000, type = "int", desc = "Maximum number of tiles filled before aborting. Larger numbers just take longer to complete." }, { name = "diagonals", required = false, default = false, type = "bool", desc = "Should the flood escape through corners?",guiOnlyArg=true }, }, runSilent = false, digMode = "@", desc = "Floodfill current designation at cursor.",aliases={"f"} }, - flood = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = true, args = { { name = "max", required = false, default = 10000, type = "int", desc = "Maximum number of tiles filled before aborting. Larger numbers just take longer to complete." }, { name = "diagonals", required = false, default = false, type = "bool", desc = "Should the flood escape through corners?" }, }, runSilent = false, digMode = "@", desc = "Floodfill current designation" }, - - resetz = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = true, args = nil, runSilent = false, digMode = "@", desc = "Move all control points to current z level" }, + resetz = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = true, args = nil, runSilent = false, digMode = "@", desc = "Move all control points to current z level",aliases={"z"} }, radial = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = true, args = { { name = "ways", required = true, default = 3, type = "int", desc = "Number of radially symetrical points to draw." }, }, runSilent = false, digMode = "@", "Draw points with radial symmetry around origin" }, --todo: code this curve = { requireOrigin = true, requireMajor = true, requireZ = true, allowFilled = false, args = { { name = "Sharpness", required = true, default = { default = 1.5, min = 0, max = 100, inc = 0.1, type = "float" }, type = "float", desc = "How strongly the curve is pulled towards the cursor" } }, runSilent = false, digMode = "@", desc = "Draw a curve (bezier) from origin to major pulled towards cursor" }, --todo: allow filled. Also draw line, then fill shape --{ default = 1.5, min = 0, max = 100, inc = 0.1, type = "float" } --arc = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = true, args = nil, runSilent = false,digMode="@",desc="Draw an arc from origin to major passing through cursor." }, - downstair = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = false, args = { { name = "depth", required = true, default = 10, type = "int", desc = "Number of z levels down to designate." }, { name = "start", required = true, default = true, type = "bool", desc = "Should the starting level be updown [false] or down [true]" }, }, runSilent = false, digMode = "@", desc = "Designate a 3x3 block of updown stairs, corners and center only" }, + downstair = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = false, args = { { name = "depth", required = true, default = 10, type = "int", desc = "Number of z levels down to designate." }, { name = "start", required = true, default = true, type = "bool", desc = "Should the starting level be updown [false] or down [true]" }, }, runSilent = false, digMode = "@", desc = "Designate a 3x3 block of updown stairs, corners and center only",aliases={"k","ks"} }, }, @@ -619,7 +618,7 @@ function DigshapeUI:updateMenuDisplay() self:updateMenuArg("digshapeMenu", index, { disabled = false }) end - printtable(v, "ARG " .. i) + --printtable(v, "ARG " .. i) self:updateMenuArg("digshapeMenu", "label_arg" .. i .. "name", { text = v.name }) self:updateMenuArg("digshapeMenu", "label_arg" .. i .. "value", { text = tostring(v.currentValue) }) nargs = nargs + 1 @@ -640,11 +639,11 @@ function DigshapeUI:updateMenuDisplay() end end - + --printtable(self.activeCommand) --update digshape command display self:rebuildDigshapeArgumentString() - self:updateMenuArg("digshapeMenu", "label_digshapeCommand", { text = self.activeCommand.digshapeString }) + self:updateMenuArg("digshapeMenu", "label_digshapeCommand", { text = self.activeCommand.digshapeString:gsub("@",self.activeDesgination) }) --update control point display/lockout showArgs(self) @@ -676,7 +675,7 @@ function DigshapeUI:updateMenuDisplay() if self.activeCommand.args ~= nil and self.activeCommand.args ~= {} then showArgs(self, command)--showargs first so that preview updates their values. else - printtable(self.activeCommand) + -- printtable(self.activeCommand) stdout("WRN", "Can't update args in menu, no args set.") end @@ -741,23 +740,23 @@ end function DigshapeUI:rebuildDigshapeArgumentString() stdout("CAL", debug.getinfo(1, 'n').name or "@ line " .. debug.getinfo(1, 'S').linedefined) + --does not set "digshape lua preview" local newstring="" local words = {} - while true do - --print(self.activeCommand.digshapeString) - local w=string.gsub(self.activeCommand.digshapeString, "^(%a*)( ?.-)$","%1" )--:sub(1) - if w=="" then - -- print("done") - break + if self.activeCommand.digshapeString~=nil then + while true do + local w=string.gsub(self.activeCommand.digshapeString, "^(%a*)( ?.-)$","%1" )--:sub(1) + if w=="" then + break + end + table.insert(words, w) + self.activeCommand.digshapeString=self.activeCommand.digshapeString:gsub(w,"") end - -- print("W='"..w.."'") - table.insert(words, w) - self.activeCommand.digshapeString=self.activeCommand.digshapeString:gsub(w,"") + newstring=words[1] + words[1]=nil + else + self.activeCommand.digshapeString="" end - self.activeCommand.digshapeString="" - printtable(words) - newstring=words[1] - words[1]=nil for index,w in ipairs(words) do print(index, w) @@ -770,7 +769,7 @@ function DigshapeUI:rebuildDigshapeArgumentString() end end end - -- print(newstring) + if self.activeCommand.args ~= nil then for k,v in pairs(self.activeCommand.args) do -- print(k) @@ -779,16 +778,27 @@ function DigshapeUI:rebuildDigshapeArgumentString() newstring=newstring.." "..tostring(v.currentValue) end end + end + --filled/hollow + local filledstring="NA" + if self.activeCommand.digshapeArgs.fill ~="NA" then + filledstring=tostring(self.activeCommand.digshapeArgs.fill) + else + filledstring="" + end + newstring=newstring.." "..filledstring + + --digMode + local digstring="@" + if self.activeCommand.digshapeArgs.digmode ~= "@" then + digstring=self.activeCommand.digshapeArgs.digmode + else + digstring=self.activeDesgination end - -- printtable(self.activeCommand) - newstring=newstring.." "..self.activeCommand.digshapeArgs.digmode + newstring=newstring.." ".. digstring self.activeCommand.digshapeString=newstring - --print("NS='"..newstring.."'") -- = self.activeCommand.digshapeString:gsub("^((digshape )?(lua )?(preview )?(%a+)(.-)$","%1%2%3%4") - - -- worms() - end function DigshapeUI:clearCommand() @@ -803,21 +813,18 @@ function DigshapeUI:clearCommand() digmode = "@", --'@': replace with current digmode. mode = "designating"--"designating" or "marking" or "toggling" } } - --printtable(self.activeCommand) + --Make the self.activeCommand.digshapeArgs correct - if self.activeCommand.allowFilled then - if currentFill ~= "NA" then - self.activeCommand.digshapeArgs.fill = "hollow" - else - self.activeCommand.digshapeArgs.fill = currentFill + if self.digshapeCommands[commandBase].allowFilled then + if currentFill ~= "NA" then + self.activeCommand.digshapeArgs.fill =currentFill + + else + self.activeCommand.digshapeArgs.fill = "hollow" + end end - else - self.activeCommand.digshapeArgs.fill = "NA" - end self.activeCommand.digshapeArgs.digmode = self.digshapeCommands[commandBase].digMode - - end function DigshapeUI:parseCommand() @@ -825,20 +832,12 @@ function DigshapeUI:parseCommand() stdout("CAL", debug.getinfo(1, 'n').name or "@ line " .. debug.getinfo(1, 'S').linedefined) stdout("MSG", "parsecommand:", self.activeCommand.name) - - --if self.activeCommand.digshapeString == nil then - --print("ac.b=", type(self.activeCommand.name)) - --printtable(self.activeCommand.name) - --reset and clear out self.activeCommand self:clearCommand() - printtable(self.activeCommand, "post clear") --Setup the command arguments (to defaults), and build the digshapeString if self.activeCommand.args ~= nil then for argi = 1, #self.activeCommand.args do - --print("-> ARG", argi) - --printtable(self.activeCommand.args[argi],"ARG "..argi) if self.activeCommand.args[argi].currentValue == nil then self.activeCommand.args[argi].currentValue = self.activeCommand.args[argi].default @@ -849,7 +848,6 @@ function DigshapeUI:parseCommand() end - --printtable(self.activeCommand) if self.activeCommand.args[argi].guiOnlyArg ~= true then self.activeCommand.digshapeString = self.activeCommand.digshapeString .. " " .. tostring(self.activeCommand.args[argi].currentValue) else @@ -867,15 +865,9 @@ function DigshapeUI:parseCommand() self.activeCommand.digshapeString = self.activeCommand.digshapeString .. " " .. self.activeCommand.digshapeArgs.digmode:gsub("@", self.activeDesgination) end - --self.activeCommand = self.activeCommand - -- end self:rebuildDigshapeArgumentString() - --self.subviews.digshapeMenu.text_ids.label_digshapeCommand.text = "[ " .. self.activeCommand.digshapeString .. " ]"--update menu display stdout("MSG", "Parsed command:", self.activeCommand.digshapeString) - --return self.activeCommand.name - printtable(self.activeCommand, "done") - end function DigshapeUI:runCurrentCommand(preview) @@ -893,20 +885,16 @@ function DigshapeUI:runCurrentCommand(preview) --printtable(self.digshapeCommands[nameCommand],i) local thisCommandIndex = nil local matchCommandIndex = nil - printtable(self.digshapeCommands[nameCommand]) + -- printtable(self.digshapeCommands[nameCommand]) for i = 1, #self.digshapeCommands[nameCommand].args do if type(self.digshapeCommands[nameCommand].args[i].default) == "table" and self.digshapeCommands[nameCommand].args[i].default.commandBase ~= nil then --get index of this arg in it's values list, and use that to select the right commandBase local swaptocommand = "" - local valueToMatch = self.activeCommand.args[i].currentValue--self.digshapeCommands[nameCommand].args[i].currentValue + local valueToMatch = self.activeCommand.args[i].currentValue for ii, vv in ipairs(self.digshapeCommands[nameCommand].args[i].default.values) do - -- print("checkmatch:", self.activeCommand.digshapeString, ii, vv,valueToMatch) - --printtable(self.digshapeCommands[nameCommand].args,i) - if self.digshapeCommands[nameCommand].args[i].default.commandBase[ii] == self.activeCommand.name then thisCommandIndex = ii end - if valueToMatch == vv then --print("matchfound", vv, ii) matchCommandIndex = ii @@ -925,7 +913,6 @@ function DigshapeUI:runCurrentCommand(preview) end end - printtable(self.activeCommand) self:rebuildDigshapeArgumentString() command = command .. " " .. self.activeCommand.digshapeString @@ -962,10 +949,6 @@ function DigshapeUI:runCurrentCommand(preview) end self:runDigshapeCommand(command) - if nameCommand ~= origCommand then - --print("now reset to", origCommand) - --self.activeCommand = oldCommand - end end function DigshapeUI:runDigshapeCommand(command) @@ -996,6 +979,8 @@ function DigshapeUI:runDigshapeCommand(command) stdout("CMD", "-------------------------------RECURSIVELY RESETZ-----------------------") self:runDigshapeCommand("digshape lua resetz") self:runDigshapeCommand(command) + else + stdout("ERR",line) end table.insert(self.currentError, messageContents) elseif messageType == "dig" then @@ -1017,7 +1002,7 @@ function DigshapeUI:runDigshapeCommand(command) table.insert(self.currentOutput, messageContents) stdout("RBY", "ref:", messageContents) else - -- stdout("ERR", "Digshape Unhandled Output:", line) + stdout("WRN", "Digshape Unhandled Output:", line) end end @@ -1081,13 +1066,18 @@ end function DigshapeUI:buttonCallback_toggleFilled() stdout("CAL", debug.getinfo(1, 'n').name or "@ line " .. debug.getinfo(1, 'S').linedefined) - self.activeCommand.digshapeArgs.fill = not self.activeCommand.digshapeArgs.fill - self.activeCommand.digshapeString = nil - self:previewCurrentCommand() - - local newvalue = self:getCurrentFill() - + if self.activeCommand.digshapeArgs.fill == "NA" then + return + end + if self.activeCommand.digshapeArgs.fill == "filled" then + self.activeCommand.digshapeArgs.fill = "hollow" + else + self.activeCommand.digshapeArgs.fill = "filled" + end + --since this is a callback we need to manually call these updates. + self:updateMenuDisplay() + self:previewCurrentCommand() end function DigshapeUI:buttonCallback_setCommand() @@ -1099,60 +1089,64 @@ function DigshapeUI:buttonCallback_setCommand() ) end -function DigshapeUI:buttonCallback_setDig(mode) +function DigshapeUI:updateDigMode(mode,set) stdout("CAL", debug.getinfo(1, 'n').name or "@ line " .. debug.getinfo(1, 'S').linedefined) - local buttonCallback_setDig_labelhelper - buttonCallback_setDig_labelhelper = function(mode, set) - set = set == "set" and "set" or "clear" - --In the button list, insert a > and < before and after the currently selected item. + set = set == "set" and "set" or "clear" + --In the button list, insert a > and < before and after the currently selected item. - local pitem = string.match(self.digButtons.order, "(.)" .. mode) - local titem = "button_digmode_" .. mode + local pitem = string.match(self.digButtons.order, "(.)" .. mode) + local titem = "button_digmode_" .. mode - if pitem == "_" then - --we're outside the list of actual buttons, use the text []. - pitem = "label_digmodeStart" - else - pitem = "button_digmode_" .. pitem - end + if pitem == "_" then + --we're outside the list of actual buttons, use the text []. + pitem = "label_digmodeStart" + else + pitem = "button_digmode_" .. pitem + end - local kpen = self.pens.digMode.deselected - local text = "" + local kpen = self.pens.digMode.deselected + local text = "" - if set == "set" then - local temp = self.pens.digMode.selected - if mode == 'x' then - temp = self.pens.digMode.delete - elseif mode == 'M' then - temp = self.pens.digMode.mark - end - kpen = temp + if set == "set" then + local temp = self.pens.digMode.selected + if mode == 'x' then + temp = self.pens.digMode.delete + elseif mode == 'M' then + temp = self.pens.digMode.mark end + kpen = temp + end - local doset - doset = function(label, kpen, text) - --for k, v in pairs(self.subviews.digmodeMenu.text_ids) do - -- print(k, v) - --end - - if text == "<" then - self.subviews.digmodeMenu.text_ids[label].key_pen = kpen - end - self.subviews.digmodeMenu.text_ids[label].pen = kpen + local doset + doset = function(label, kpen, text,set) + if text == "<" then + self.subviews.digmodeMenu.text_ids[label].key_pen = kpen + end + self.subviews.digmodeMenu.text_ids[label].pen = kpen + if set=="set" then self.subviews.digmodeMenu.text_ids[label].text = " "--text + else + self.subviews.digmodeMenu.text_ids[label].text ="" end - - doset(pitem, kpen, ">")--before - doset(titem, kpen, "<")--thisitem end - buttonCallback_setDig_labelhelper(self.activeDesgination, "clear") - buttonCallback_setDig_labelhelper(mode, "set") + doset(pitem, kpen, ">", set)--before + doset(titem, kpen, "<", set)--thisitem +end + +function DigshapeUI:buttonCallback_setDig(mode) + stdout("CAL", debug.getinfo(1, 'n').name or "@ line " .. debug.getinfo(1, 'S').linedefined) + local buttonCallback_setDig_labelhelper + + + + self:updateDigMode(self.activeDesgination, "clear") + self:updateDigMode(mode, "set") self.subviews.digmodeMenu.text_ids["label_digmodeName"].text = self.digButtons[mode].text --do the update: self.activeDesgination = mode - self.activeCommand.digshapeString = nil --regen digshape command + --regen digshape command self:parseCommand() self:previewCurrentCommand() end @@ -1313,8 +1307,10 @@ function DigshapeUI:renderOverlay() paintMapTile(dc, vp, df.global.cursor, xyz2pos(dig.x, dig.y, dig.z), dig.symbol, self.activeDesgination == 'x' and self.pens['clear'] or self.pens['designation']) end - if self.origin then - paintMapTile(dc, vp, df.global.cursor, self.origin, '+', self.pens['origin']) + if self.digshapeCommands[self.activeCommand.name].requireOrigin then + if self.origin then + paintMapTile(dc, vp, df.global.cursor, self.origin, '+', self.pens['origin']) + end end if self.activeCommand.name ~= nil then if self.major then From ea19d748e62a21957cd672e50c4a069290682121 Mon Sep 17 00:00:00 2001 From: Quatch Date: Thu, 1 Apr 2021 12:07:18 -0400 Subject: [PATCH 10/12] Fixed floodfill and sped it up --- digshape.rb | 103 +++++++++++++++++++++++++++++++---------------- gui/digshape.lua | 35 +++++++++++++--- 2 files changed, 98 insertions(+), 40 deletions(-) diff --git a/digshape.rb b/digshape.rb index 5cfacfb..34f853c 100644 --- a/digshape.rb +++ b/digshape.rb @@ -1106,7 +1106,11 @@ def floodfill(x,y,z,targetDig, digMode, maxCounter= 10000) checkedTiles=Hash.new #these keep us from rechecking tiles and such. designatedTiles=Hash.new + loopcounter=1 + loop do +# puts "Loop#: "+ loopcounter.to_s + loopcounter=loopcounter+1 if xStack.length <=0 then # puts "Stack empty." break @@ -1115,7 +1119,7 @@ def floodfill(x,y,z,targetDig, digMode, maxCounter= 10000) x= xw = xe = xStack.pop() y = yStack.pop() #always push/pop x&y together. - #puts "Checking: "+x.to_s+","+y.to_s +# puts "Checking: "+x.to_s+","+y.to_s if checkedTiles[x.to_s+","+y.to_s] !=nil then #puts x.to_s+","+y.to_s+" already checked" next @@ -1125,59 +1129,86 @@ def floodfill(x,y,z,targetDig, digMode, maxCounter= 10000) end #search W for bounds - loop do - xi = xw - 1 #move xw cursor west until it hits a match - t=df.map_tile_at(xi,y,z) - - if !t || xi == 0 || t.designation.dig != targetDig || !isDigPermitted(digMode,t.shape_basic) then - break + if checkedTiles[(x-1).to_s+","+y.to_s] == nil then + #don't scan west if we've already tried that from the tile to the left. + + loop do + xi = xw - 1 #move xw cursor west until it hits a match + t=df.map_tile_at(xi,y,z) + + if !t || xi == 0 || t.designation.dig != targetDig || !isDigPermitted(digMode,t.shape_basic) then + break + end + xw = xi end - xw = xi - end - - #search E for bounds - loop do - xi = xe + 1 #move xe cursor east until it hits a match - t=df.map_tile_at(xi,y,z) - - if !t || xi == 0 || t.designation.dig != targetDig || !isDigPermitted(digMode,t.shape_basic) then - break + + #search E for bounds + if checkedTiles[(x+1).to_s+","+y.to_s] == nil then + #don't scan east if we've already tried that from the tile to the left. + loop do + xi = xe + 1 #move xe cursor east until it hits a match + t=df.map_tile_at(xi,y,z) + + if !t || xi == 0 || t.designation.dig != targetDig || !isDigPermitted(digMode,t.shape_basic) then + break + end + xe = xi + end + + else + # puts "don't scan west: "+(x-1).to_s+","+y.to_s+" already checked." end - xe = xi + else + #puts "don't scan west: "+(x-1).to_s+","+y.to_s+" already checked." end #scan W..E filling, and checking N/S for xi in xw..xe do - #puts "scan: "+xi.to_s+","+y.to_s+" = "+designatedTiles[xi.to_s+","+y.to_s].to_s +# puts "scan: "+xi.to_s+","+y.to_s+" = "+designatedTiles[xi.to_s+","+y.to_s].to_s if designatedTiles[xi.to_s+","+y.to_s] == nil then + counter = counter -1 #only decrease counter based on tiles dug, not tiles checked. digAt(xi, y, z, digMode) designatedTiles[xi.to_s+","+y.to_s]=true - #puts "Dig: "+xi.to_s+","+y.to_s +# puts "Dig: "+xi.to_s+","+y.to_s else - #puts "Nodig: "+xi.to_s+","+y.to_s+" ="+designatedTiles[xi.to_s+","+y.to_s].to_s+"/" +# puts "Nodig: "+xi.to_s+","+y.to_s+" ="+designatedTiles[xi.to_s+","+y.to_s].to_s+"/" end - counter = counter -1 - if counter <=0 then + + if counter <=0 then + puts "Loop#: "+ loopcounter.to_s stdout(" Max coverage of #{maxCounter} tiles reached. Use multiple floods, or add a number for max coverage as 'digshape flood [max coverage] [dig type]'.") - stdout(" Automatically cancelling flood") - undo() + #stdout(" Automatically cancelling flood") + #undo() return end + if loopcounter >=100000 then + puts "Loop#: "+ loopcounter.to_s + stdout(" Runaway flood algorithm, abort!") + undo() + stderr(" Automatically cancelling flood.") + return + end #check N/S - t = df.map_tile_at(xi,y+1,z) - if t && t.designation.dig == targetDig && isDigPermitted(digMode,t.shape_basic) then - #puts "push"+xi.to_s+" "+(y+1).to_s+" as row down" - xStack.push(xi) - yStack.push(y+1) - end - t = df.map_tile_at(xi,y-1,z) - if t && t.designation.dig == targetDig && isDigPermitted(digMode,t.shape_basic) then - xStack.push(xi) - yStack.push(y-1) + if checkedTiles[(x).to_s+","+(y+1).to_s] == nil then + #don't recheck South + t = df.map_tile_at(xi,y+1,z) + if t && t.designation.dig == targetDig && isDigPermitted(digMode,t.shape_basic) then + #puts "push"+xi.to_s+" "+(y+1).to_s+" as row down" + xStack.push(xi) + yStack.push(y+1) + end + end + if checkedTiles[(x).to_s+","+(y-1).to_s] == nil then + #don't recheck north + t = df.map_tile_at(xi,y-1,z) + if t && t.designation.dig == targetDig && isDigPermitted(digMode,t.shape_basic) then + xStack.push(xi) + yStack.push(y-1) + end end end @@ -1185,6 +1216,7 @@ def floodfill(x,y,z,targetDig, digMode, maxCounter= 10000) break end end + #puts "Loop#: "+ loopcounter.to_s end @@ -1582,6 +1614,7 @@ def noMoreArguments(args) digKeupoStair(df.cursor.x, df.cursor.y, df.cursor.z, depth) when 'flood', 'f' + stdout("cursor: #{cursorAsDigPos().to_s}") maxArea = getIntegerArgument($script_args, default: 10000, type: "maximum flood area") digMode = getDigModeArgument($script_args) diff --git a/gui/digshape.lua b/gui/digshape.lua index ed286b8..803140e 100644 --- a/gui/digshape.lua +++ b/gui/digshape.lua @@ -133,7 +133,7 @@ if verbose then local _, _, temp = string.find(msgtype, "^(...)$") if temp == "ERR" or temp == "MSG" or temp == "WRN" or temp == "OUT" or temp == "CMD" or temp == "RBY" or temp == ">>>" or temp == "CAL" then --output channels: MSG/WRN/ERR: information, OUT: results, RBY: digshape output passed through. - if temp == "IGNORE" or temp == "ALSOIGNORE" or temp == "RBY5" then + if temp == "IGNORE" or temp == "ALSOIGNORE" or temp == "5RBY" then return --don't print end local color = { RBY = COLOR_DARKGREY, MSG = COLOR_WHITE, WRN = COLOR_YELLOW, ERR = COLOR_LIGHTRED, CMD = COLOR_LIGHTCYAN, OUT = COLOR_MAGENTA, [">>>"] = COLOR_LIGHTMAGENTA, CAL = COLOR_BROWN } @@ -302,9 +302,9 @@ DigshapeUI.ATTRS { star = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = false, args = { { name = "points", required = true, default = 5, type = "int", desc = "Number of points of the star" }, { name = "skip", required = true, default = 2, type = "int", desc = "How many to skip when connecting...?" }, }, runSilent = false, digMode = "@", desc = "Draw a star", aliases = { "s", "st" } }, - spiral = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = false, args = { { name = "coils", required = true, default = 2, type = "int", desc = "Number of turns the spiral makes." }, { name = "skip", required = true, default = 1, type = "int", desc = "Draw every # points along spiral." }, { name = "rotate", required = true, default = { default = 0, mod = 360, inc = 15, type = "int" }, type = "int", desc = "Rotate the spiral, 0-360." }, }, runSilent = false, digMode = "@", desc = "Draw a spiral", aliases = { "sp", "coil" } }, + spiral = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = false, args = { { name = "coils", required = true, default = { default = 2, min = 1, max = 1000, inc = 1, type = "int" }, type = "int", desc = "Number of turns the spiral makes." }, { name = "skip", required = true, default = { default = 1, min = 1, max = 1000, inc = 1, type = "int" }, type = "int", desc = "Draw every # points along spiral." }, { name = "rotate", required = true, default = { default = 0, mod = 360, inc = 15, type = "int" }, type = "int", desc = "Rotate the spiral, 0-360." }, }, runSilent = false, digMode = "@", desc = "Draw a spiral", aliases = { "sp", "coil" } }, - flood = { requireOrigin = false, requireMajor = false, requireZ = false, allowFilled = false, args = { { name = "max", required = false, default = 10000,inc=5000, type = "int", desc = "Maximum number of tiles filled before aborting. Larger numbers just take longer to complete." }, { name = "diagonals", required = false, default = false, type = "bool", desc = "Should the flood escape through corners?",guiOnlyArg=true }, }, runSilent = false, digMode = "@", desc = "Floodfill current designation at cursor.",aliases={"f"} }, + flood = { requireOrigin = false, requireMajor = false, requireZ = false, allowFilled = false, args = { { name = "max", required = false, default = { default = 2000, min = 1,max=10000, inc = 1000, type = "int" }, type = "int", desc = "Maximum number of tiles filled before aborting. Larger numbers just take longer to complete." }, { name = "diagonals", required = false, default = false, type = "bool", desc = "Should the flood escape through corners?",guiOnlyArg=true }, }, runSilent = false, digMode = "@", desc = "Floodfill current designation at cursor.",aliases={"f"} }, resetz = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = true, args = nil, runSilent = false, digMode = "@", desc = "Move all control points to current z level",aliases={"z"} }, radial = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = true, args = { { name = "ways", required = true, default = 3, type = "int", desc = "Number of radially symetrical points to draw." }, }, runSilent = false, digMode = "@", "Draw points with radial symmetry around origin" }, --todo: code this @@ -486,6 +486,10 @@ function DigshapeUI:init() -- on_activate = self:callback('buttonCallback_showHelpPopup'), --}, NEWLINE, + { text = "CTRL+move: Move origin & cursor", key_sep = "", + on_activate = self:callback('buttonCallback_dualmove'), + }, NEWLINE, + { key = "STRING_A092", text = "Move view to see origin", key_sep = ": ", on_activate = self:callback('buttonCallback_recenterView'), }, NEWLINE, @@ -686,7 +690,8 @@ end --self:updateMenuArg("","",{}) function DigshapeUI:updateMenuArg(menu, textID, newvalues) - stdout("CAL", debug.getinfo(1, 'n').name or "@ line " .. debug.getinfo(1, 'S').linedefined) + if type(verbose)=="int" and verbose >1 then stdout("CAL", debug.getinfo(1, 'n').name or "@ line " .. debug.getinfo(1, 'S').linedefined) + end local temp = self.subviews[menu].text_ids[textID] if type(newvalues) == "table" then @@ -1002,7 +1007,7 @@ function DigshapeUI:runDigshapeCommand(command) table.insert(self.currentOutput, messageContents) stdout("RBY", "ref:", messageContents) else - stdout("WRN", "Digshape Unhandled Output:", line) + --stdout("WRN", "Digshape Unhandled Output:", line) end end @@ -1266,10 +1271,29 @@ function DigshapeUI:buttonCallback_showHelpPopup() stdout("CAL", debug.getinfo(1, 'n').name or "@ line " .. debug.getinfo(1, 'S').linedefined) end + +function DigshapeUI:buttonCallback_dualmove() +--move origin and cursor by same amount as current move key. + --todo: code this. +end + + +--function DigshapeUI:buttonCallback_() +-- +--end + +--function DigshapeUI:buttonCallback_() +-- +--end + +--function DigshapeUI:buttonCallback_() -- +--end + --function DigshapeUI:buttonCallback_() -- --end + --function DigshapeUI:buttonCallback_() -- --end @@ -1345,6 +1369,7 @@ function DigshapeUI:onInput(keys) --TODO: deal with multi-key presses, because keys is an array of individuals. if df.global.cursor.x == -30000 then + stdout("ERR","Cursor offscreen, resetting.") local vp = self:getViewport() df.global.cursor = xyz2pos(math.floor((vp.x1 + math.abs((vp.x2 - vp.x1)) / 2) + .5), math.floor((vp.y1 + math.abs((vp.y2 - vp.y1) / 2)) + .5), vp.z) return From b2d0100a15144fbe71f032f79fbe523d43c50c60 Mon Sep 17 00:00:00 2001 From: Quatch Date: Fri, 2 Apr 2021 21:54:01 -0400 Subject: [PATCH 11/12] Add: flood shortcut. Bugfixes: ellipse3p. Fixed (again) ellipse3p aka 2axis mode. Added Alt+f as a floodfill at cursor shortcut --- digshape.rb | 22 +++--- gui/digshape.lua | 194 ++++++++++++++++++++++++++++------------------- 2 files changed, 128 insertions(+), 88 deletions(-) diff --git a/digshape.rb b/digshape.rb index 34f853c..306e952 100644 --- a/digshape.rb +++ b/digshape.rb @@ -945,16 +945,18 @@ def digKeupoStair(x, y, z, depth,differentStart=true) #Dig an X of updown stairs (corners and center of a 3x3) centered on cursor, down a number of zlevels. iz = z startingDesignation='' - if (differentStart) then - startingDesignation='j' - else - startingDesignation='i' - end + digAt(x, y, iz, 'j') digAt(x - 1, y + 1, iz, 'j') digAt(x - 1, y - 1, iz, 'j') digAt(x + 1, y + 1, iz, 'j') digAt(x + 1, y - 1, iz, 'j') + if (differentStart) then + startingDesignation='j' + iz=iz-1 + else + startingDesignation='i' + end while iz >= z - (depth - 1) do digAt(x, y, iz, 'i') digAt(x - 1, y + 1, iz, 'i') @@ -1303,7 +1305,6 @@ def getFilledArgument(args, default: false) when 'filled', 'f', 'true', 'yes', 'y'; filled = true when 'hollow', 'h', 'false', 'no', 'n'; filled = false else - puts( "DEFAULT?>@#$#@") return default # doesn't consume if nothing matches end args.delete_at(0); @@ -1389,12 +1390,13 @@ def getBooleanArgument(args, default: nil, type: "(unnamed integer)") when 'default','-'; userSucks("No default value for #{type} parameter!") if default == nil result = default + when 'true', true; + result=true + when 'false',false; + return false else result = num==true rescue userSucks("Malformed boolean for "+type+" parameter, got `"+num+"'.#{defaultMessage}") end - - - return result end @@ -1611,7 +1613,7 @@ def noMoreArguments(args) userSucks("Depth must be an integer greater than zero") end - digKeupoStair(df.cursor.x, df.cursor.y, df.cursor.z, depth) + digKeupoStair(df.cursor.x, df.cursor.y, df.cursor.z, depth,start) when 'flood', 'f' stdout("cursor: #{cursorAsDigPos().to_s}") diff --git a/gui/digshape.lua b/gui/digshape.lua index 803140e..7c1c93e 100644 --- a/gui/digshape.lua +++ b/gui/digshape.lua @@ -1,7 +1,6 @@ --gui front-end for digshape.rb, a geometric designations generating tool ---[====[ -gui/digshape +--[====[ =========== gui front-end for digshape.rb, a geometric designations generating tool @@ -9,17 +8,34 @@ gui front-end for digshape.rb, a geometric designations generating tool ]====] verbose = false --TODO: move these down later to be arguments +DigshapeUIversion = "20210402" --dfhack.screen.invalidate() --force an immediate redraw. --TODO: use dfhack.print(args...) for better printing -function printtable(table, note, recursecount, recurselimit) +function shallowcopy(orig) + --tired of trying to figure out when lua will alter the wrong thing. From http://lua-users.org/wiki/CopyTable + local orig_type = type(orig) + local copy + if orig_type == 'table' then + copy = {} + for orig_key, orig_value in pairs(orig) do + copy[orig_key] = orig_value + end + else + -- number, string, boolean, etc + copy = orig + end + return copy +end + +function printtable(table, note, recursecount) --pretty print a table, adds $note and source line number as a header --printall_recurse(obj)..sigh. Why couldn't I find this when I went looking for it. if not verbose then return end local prefix = note or "|" - local recursecount = recursecount or 5 + local recursecount = recursecount or 5 --how deep down into the nested tables are we allowed? hack circular protection, plus it's all we really need. local title = "" if not string.match(prefix, "^ *|$") then title = " " .. prefix .. " " @@ -133,7 +149,7 @@ if verbose then local _, _, temp = string.find(msgtype, "^(...)$") if temp == "ERR" or temp == "MSG" or temp == "WRN" or temp == "OUT" or temp == "CMD" or temp == "RBY" or temp == ">>>" or temp == "CAL" then --output channels: MSG/WRN/ERR: information, OUT: results, RBY: digshape output passed through. - if temp == "IGNORE" or temp == "ALSOIGNORE" or temp == "5RBY" then + if temp == "IGNORE" or temp == "ALSOIGNORE" or temp == "RjBY" then return --don't print end local color = { RBY = COLOR_DARKGREY, MSG = COLOR_WHITE, WRN = COLOR_YELLOW, ERR = COLOR_LIGHTRED, CMD = COLOR_LIGHTCYAN, OUT = COLOR_MAGENTA, [">>>"] = COLOR_LIGHTMAGENTA, CAL = COLOR_BROWN } @@ -288,7 +304,7 @@ DigshapeUI.ATTRS { controla = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = true, args = nil, runSilent = false, digMode = "@", desc = "Set the first control point (A, or 'major')" }, swap = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = true, args = nil, runSilent = false, digMode = "@", desc = "Swap the origin and cursor" }, - allCommands = { requireOrigin = true, requireMajor = false, requireZ = false, allowFilled = false, args = { "command", default = "circle", values = { "circle", "line", "curve", "ellipse", "star", "polygon", "spiral" }, commandBase = { "circle", "line", "curve", "ellipse", "star", "polygon", "spiral" }, type = "string", runSilent = false, digMode = "@", desc = "Command selection" }, }, + allCommands = { requireOrigin = true, requireMajor = false, requireZ = false, allowFilled = false, args = { "command", default = "circle", values = { "circle", "line", "curve", "ellipse", "star", "polygon", "spiral" }, commandBase = { "circle", "line", "curve", "ellipse", "star", "polygon", "spiral" }, type = "string", runSilent = false, digMode = "@", desc = "Command selection" }, aliases = { "all" } }, circle = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = true, args = nil, runSilent = false, digMode = "@", desc = "Draw a circle", aliases = { "c", "c2", "circle2p" } }, @@ -302,16 +318,16 @@ DigshapeUI.ATTRS { star = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = false, args = { { name = "points", required = true, default = 5, type = "int", desc = "Number of points of the star" }, { name = "skip", required = true, default = 2, type = "int", desc = "How many to skip when connecting...?" }, }, runSilent = false, digMode = "@", desc = "Draw a star", aliases = { "s", "st" } }, - spiral = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = false, args = { { name = "coils", required = true, default = { default = 2, min = 1, max = 1000, inc = 1, type = "int" }, type = "int", desc = "Number of turns the spiral makes." }, { name = "skip", required = true, default = { default = 1, min = 1, max = 1000, inc = 1, type = "int" }, type = "int", desc = "Draw every # points along spiral." }, { name = "rotate", required = true, default = { default = 0, mod = 360, inc = 15, type = "int" }, type = "int", desc = "Rotate the spiral, 0-360." }, }, runSilent = false, digMode = "@", desc = "Draw a spiral", aliases = { "sp", "coil" } }, + spiral = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = false, args = { { name = "coils", required = true, default = { default = 2, min = 1, max = 1000, inc = 1, type = "int" }, type = "int", desc = "Number of turns the spiral makes." }, { name = "skip", required = true, default = { default = 1, min = 1, max = 1000, inc = 1, type = "int" }, type = "int", desc = "Draw every # points along spiral." }, { name = "rotate", required = true, default = { default = 0, mod = 360, inc = 15, type = "int" }, type = "int", desc = "Rotate the spiral, 0-360." }, }, runSilent = false, digMode = "@", desc = "Draw a spiral", aliases = { "sp", "coil" } }, - flood = { requireOrigin = false, requireMajor = false, requireZ = false, allowFilled = false, args = { { name = "max", required = false, default = { default = 2000, min = 1,max=10000, inc = 1000, type = "int" }, type = "int", desc = "Maximum number of tiles filled before aborting. Larger numbers just take longer to complete." }, { name = "diagonals", required = false, default = false, type = "bool", desc = "Should the flood escape through corners?",guiOnlyArg=true }, }, runSilent = false, digMode = "@", desc = "Floodfill current designation at cursor.",aliases={"f"} }, + flood = { requireOrigin = false, requireMajor = false, requireZ = false, allowFilled = false, args = { { name = "max", required = false, default = { default = 2000, min = 1, max = 10000, inc = 1000, type = "int" }, type = "int", desc = "Maximum number of tiles filled before aborting. Larger numbers just take longer to complete." }, { name = "diagonals", required = false, default = false, type = "bool", desc = "Should the flood escape through corners?", guiOnlyArg = true }, }, runSilent = false, digMode = "@", desc = "Floodfill current designation at cursor.", aliases = { "f", "fl", "fill", "floodfill" } }, - resetz = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = true, args = nil, runSilent = false, digMode = "@", desc = "Move all control points to current z level",aliases={"z"} }, - radial = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = true, args = { { name = "ways", required = true, default = 3, type = "int", desc = "Number of radially symetrical points to draw." }, }, runSilent = false, digMode = "@", "Draw points with radial symmetry around origin" }, --todo: code this - curve = { requireOrigin = true, requireMajor = true, requireZ = true, allowFilled = false, args = { { name = "Sharpness", required = true, default = { default = 1.5, min = 0, max = 100, inc = 0.1, type = "float" }, type = "float", desc = "How strongly the curve is pulled towards the cursor" } }, runSilent = false, digMode = "@", desc = "Draw a curve (bezier) from origin to major pulled towards cursor" }, --todo: allow filled. Also draw line, then fill shape + resetz = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = true, args = nil, runSilent = false, digMode = "@", desc = "Move all control points to current z level", aliases = { "z" } }, + radial = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = true, args = { { name = "ways", required = true, default = 3, type = "int", desc = "Number of radially symetrical points to draw." }, }, runSilent = false, digMode = "@", "Draw points with radial symmetry around origin", aliases = { "r", "rad" } }, --todo: code this + curve = { requireOrigin = true, requireMajor = true, requireZ = true, allowFilled = false, args = { { name = "Sharpness", required = true, default = { default = 1.5, min = 0, max = 100, inc = 0.1, type = "float" }, type = "float", desc = "How strongly the curve is pulled towards the cursor" } }, runSilent = false, digMode = "@", desc = "Draw a curve (bezier) from origin to major pulled towards cursor", aliases = { "cu", "b", "bezier", "bez", "bezeir" } }, --todo: allow filled. Also draw line, then fill shape --{ default = 1.5, min = 0, max = 100, inc = 0.1, type = "float" } --arc = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = true, args = nil, runSilent = false,digMode="@",desc="Draw an arc from origin to major passing through cursor." }, - downstair = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = false, args = { { name = "depth", required = true, default = 10, type = "int", desc = "Number of z levels down to designate." }, { name = "start", required = true, default = true, type = "bool", desc = "Should the starting level be updown [false] or down [true]" }, }, runSilent = false, digMode = "@", desc = "Designate a 3x3 block of updown stairs, corners and center only",aliases={"k","ks"} }, + downstair = { requireOrigin = false, requireMajor = false, requireZ = false, allowFilled = false, args = { { name = "depth", required = true, default = 10, type = "int", desc = "Number of z levels down to designate." }, { name = "start", required = true, default = true, type = "bool", desc = "Should the starting level be updown [false] or down [true]" }, }, runSilent = false, digMode = "", desc = "Designate a 3x3 block of updown stairs, corners and center only", aliases = { "k", "ks", "stair", "down", "downstair", "downstairs", "stairs" } }, }, @@ -365,9 +381,9 @@ function DigshapeUI:init() { key = "CUSTOM_P", text = "Set digshape command", key_sep = ": ", on_activate = self:callback('buttonCallback_setCommand'), }, NEWLINE, - {text="[ ", pen = COLOR_YELLOW, gap = 2}, + { text = "[ ", pen = COLOR_YELLOW, gap = 2 }, { text = "[ digshape command ]", pen = COLOR_YELLOW, id = "label_digshapeCommand" }, - {text=" ]", pen = COLOR_YELLOW},NEWLINE, + { text = " ]", pen = COLOR_YELLOW }, NEWLINE, NEWLINE, @@ -475,6 +491,14 @@ function DigshapeUI:init() { text = "]: ", pen = { CLEAR_PEN, bg = COLOR_BLACK }, }, { text = "digmode name", id = "label_digmodeName" }, --{ text = " ]" },NEWLINE, + + NEWLINE, NEWLINE, + { key = "CUSTOM_ALT_F", text = "Floodfill at cursor", key_sep = ": ", + on_activate = self:callback('buttonCallback_floodAtCursor'), + }, + NEWLINE, + + }, }, @@ -521,7 +545,8 @@ function DigshapeUI:init() --display the current command preview --self:buttonCallback_setDig(self.activeDesgination) self:setCommand(self.activeCommand.name) - --self:updateMenuDisplay() + self:buttonCallback_setDig("d") + self:updateMenuDisplay() --self:previewCurrentCommand() --dfhack.gui.revealInDwarfmodeMap(self.origin) end @@ -647,7 +672,7 @@ function DigshapeUI:updateMenuDisplay() --update digshape command display self:rebuildDigshapeArgumentString() - self:updateMenuArg("digshapeMenu", "label_digshapeCommand", { text = self.activeCommand.digshapeString:gsub("@",self.activeDesgination) }) + self:updateMenuArg("digshapeMenu", "label_digshapeCommand", { text = self.activeCommand.digshapeString:gsub("@", self.activeDesgination) }) --update control point display/lockout showArgs(self) @@ -661,7 +686,7 @@ function DigshapeUI:updateMenuDisplay() print("enable alert major") self:updateMenuArg("controlPointsMenu", "button_setCtrlA", { dpen = self.pens.alertMenu, disabled = false }) end - print("enable major") + --print("enable major") self:updateMenuArg("controlPointsMenu", "button_setCtrlA", { dpen = self.pens.enabledMenu, disabled = false }) else --print("disable major") @@ -679,7 +704,7 @@ function DigshapeUI:updateMenuDisplay() if self.activeCommand.args ~= nil and self.activeCommand.args ~= {} then showArgs(self, command)--showargs first so that preview updates their values. else - -- printtable(self.activeCommand) + -- printtable(self.activeCommand) stdout("WRN", "Can't update args in menu, no args set.") end @@ -690,7 +715,8 @@ end --self:updateMenuArg("","",{}) function DigshapeUI:updateMenuArg(menu, textID, newvalues) - if type(verbose)=="int" and verbose >1 then stdout("CAL", debug.getinfo(1, 'n').name or "@ line " .. debug.getinfo(1, 'S').linedefined) + if type(verbose) == "int" and verbose > 1 then + stdout("CAL", debug.getinfo(1, 'n').name or "@ line " .. debug.getinfo(1, 'S').linedefined) end local temp = self.subviews[menu].text_ids[textID] @@ -746,63 +772,63 @@ end function DigshapeUI:rebuildDigshapeArgumentString() stdout("CAL", debug.getinfo(1, 'n').name or "@ line " .. debug.getinfo(1, 'S').linedefined) --does not set "digshape lua preview" - local newstring="" + local newstring = "" local words = {} - if self.activeCommand.digshapeString~=nil then + if self.activeCommand.digshapeString ~= nil then while true do - local w=string.gsub(self.activeCommand.digshapeString, "^(%a*)( ?.-)$","%1" )--:sub(1) - if w=="" then + local w = string.gsub(self.activeCommand.digshapeString, "^(%S*)( ?.-)$", "%1")--:sub(1) + if w == "" then break end table.insert(words, w) - self.activeCommand.digshapeString=self.activeCommand.digshapeString:gsub(w,"") + self.activeCommand.digshapeString = self.activeCommand.digshapeString:gsub(w, "") end - newstring=words[1] - words[1]=nil + newstring = words[1] + words[1] = nil else - self.activeCommand.digshapeString="" + self.activeCommand.digshapeString = "" end - for index,w in ipairs(words) do + for index, w in ipairs(words) do print(index, w) if w == "digshape" or w == "lua" or w == "preview" then - newstring=newstring.." "..w + newstring = newstring .. " " .. w end - for k,v in pairs(self.digshapeCommands) do - if w==k then - newstring=newstring.." "..w:tostring() + for k, v in pairs(self.digshapeCommands) do + if w == k then + newstring = newstring .. " " .. w:tostring() end end end if self.activeCommand.args ~= nil then - for k,v in pairs(self.activeCommand.args) do + for k, v in pairs(self.activeCommand.args) do -- print(k) -- printtable(v) - if v.guiOnlyArg==nil then - newstring=newstring.." "..tostring(v.currentValue) + if v.guiOnlyArg == nil then + newstring = newstring .. " " .. tostring(v.currentValue) end end end - --filled/hollow - local filledstring="NA" - if self.activeCommand.digshapeArgs.fill ~="NA" then - filledstring=tostring(self.activeCommand.digshapeArgs.fill) + local filledstring = "NA" + if self.activeCommand.digshapeArgs.fill ~= "NA" then + filledstring = tostring(self.activeCommand.digshapeArgs.fill) else - filledstring="" + filledstring = "" + end + if filledstring ~= "" then + newstring = newstring .. " " .. filledstring end - newstring=newstring.." "..filledstring - --digMode - local digstring="@" + local digstring = "@" if self.activeCommand.digshapeArgs.digmode ~= "@" then - digstring=self.activeCommand.digshapeArgs.digmode + digstring = self.activeCommand.digshapeArgs.digmode:gsub("%s", "") else - digstring=self.activeDesgination + digstring = self.activeDesgination end - newstring=newstring.." ".. digstring - self.activeCommand.digshapeString=newstring + newstring = newstring .. " " .. digstring + self.activeCommand.digshapeString = newstring -- = self.activeCommand.digshapeString:gsub("^((digshape )?(lua )?(preview )?(%a+)(.-)$","%1%2%3%4") end @@ -810,10 +836,10 @@ function DigshapeUI:clearCommand() stdout("CAL", debug.getinfo(1, 'n').name or "@ line " .. debug.getinfo(1, 'S').linedefined) stdout(">>>", "Clear Command") - local currentFill = self.activeCommand.digshapeArgs.fill -- save this so it carries between commands + local currentFill = shallowcopy(self.activeCommand.digshapeArgs.fill) -- save this so it carries between commands local commandBase = self.activeCommand.name--:lower():match("^%a+") self.activeCommand = self.resetActiveCommand() - self.activeCommand = { name = commandBase, args = self.digshapeCommands[commandBase].args, digshapeString = commandBase, digshapeArgs = { + self.activeCommand = { name = commandBase, args = shallowcopy(self.digshapeCommands[commandBase].args), digshapeString = commandBase, digshapeArgs = { fill = "NA", --NA if unsupported by this command, "filled" or "hollow" if digshape supports it for this command digmode = "@", --'@': replace with current digmode. mode = "designating"--"designating" or "marking" or "toggling" @@ -821,15 +847,24 @@ function DigshapeUI:clearCommand() --Make the self.activeCommand.digshapeArgs correct if self.digshapeCommands[commandBase].allowFilled then - if currentFill ~= "NA" then - self.activeCommand.digshapeArgs.fill =currentFill + if currentFill ~= "NA" then + self.activeCommand.digshapeArgs.fill = currentFill - else - self.activeCommand.digshapeArgs.fill = "hollow" - end + else + self.activeCommand.digshapeArgs.fill = "hollow" + end + end + self.activeCommand.digshapeArgs.digmode = shallowcopy(self.digshapeCommands[commandBase].digMode) + + --make sure any commandBase args are set to not autoswap + if self.activeCommand.args ~= nil then + for argname, arg in pairs(self.activeCommand.args) do + if type(arg.default) == "table" and arg.default.commandBase ~= nil then + arg.currentValue = arg.default.default + end end - self.activeCommand.digshapeArgs.digmode = self.digshapeCommands[commandBase].digMode + end end function DigshapeUI:parseCommand() @@ -856,7 +891,7 @@ function DigshapeUI:parseCommand() if self.activeCommand.args[argi].guiOnlyArg ~= true then self.activeCommand.digshapeString = self.activeCommand.digshapeString .. " " .. tostring(self.activeCommand.args[argi].currentValue) else - -- print("don't add this arg(" .. self.activeCommand.name .. ":" .. self.activeCommand.args[argi].name .. ") to digshape, internal use only.") + -- print("don't add this arg(" .. self.activeCommand.name .. ":" .. self.activeCommand.args[argi].name .. ") to digshape, internal use only.") end end @@ -877,7 +912,7 @@ end function DigshapeUI:runCurrentCommand(preview) stdout("CAL", debug.getinfo(1, 'n').name or "@ line " .. debug.getinfo(1, 'S').linedefined) - -- print(">>>>", self.activeCommand.name, self.activeCommand.digshapeString) + -- print(">>>>", self.activeCommand.name, self.activeCommand.digshapeString) local command = ("digshape lua %s"):format(preview and "preview" or "") --Check for a BASECOMMAND argument, in case this is a command that needs to be run as another command. (eg. GUI ellipse runs both "ellipse bbox" and "ellipse 2axis") @@ -890,7 +925,7 @@ function DigshapeUI:runCurrentCommand(preview) --printtable(self.digshapeCommands[nameCommand],i) local thisCommandIndex = nil local matchCommandIndex = nil - -- printtable(self.digshapeCommands[nameCommand]) + -- printtable(self.digshapeCommands[nameCommand]) for i = 1, #self.digshapeCommands[nameCommand].args do if type(self.digshapeCommands[nameCommand].args[i].default) == "table" and self.digshapeCommands[nameCommand].args[i].default.commandBase ~= nil then --get index of this arg in it's values list, and use that to select the right commandBase @@ -908,7 +943,8 @@ function DigshapeUI:runCurrentCommand(preview) if thisCommandIndex ~= matchCommandIndex then swaptocommand = self.digshapeCommands[nameCommand].args[i].default.commandBase[matchCommandIndex] - stdout(">>>", "Command swap: ", self.activeCommand.name, swaptocommand, thisCommandIndex, matchCommandIndex) + --printtable(self.activeCommand) + stdout(">>>", "Command swap: ", self.activeCommand.name, "to", swaptocommand, thisCommandIndex, matchCommandIndex) oldCommand = self.activeCommand self:clearCommand() self:setCommand(swaptocommand) @@ -948,7 +984,7 @@ function DigshapeUI:runCurrentCommand(preview) return nil end --if commandTests.requireZ==true and self.origin==nil then return nil end - if commandTests.digMode ~= "@" and commandTests.digMod ~= self.activeDesgination then + if ((commandTests.digMode ~= "@" and commandTests.digMode ~= "") and commandTests.digMode ~= self.activeDesgination) then stdout("WRN", "Current command will not exicute, skipping. 4") return nil end @@ -961,7 +997,7 @@ function DigshapeUI:runDigshapeCommand(command) --simple validity checks stdout("CMD", "RUNNING: '" .. command .. "'") if command == nil then - stdout("ERR","nil command") + stdout("ERR", "nil command") return nil end @@ -985,7 +1021,7 @@ function DigshapeUI:runDigshapeCommand(command) self:runDigshapeCommand("digshape lua resetz") self:runDigshapeCommand(command) else - stdout("ERR",line) + stdout("ERR", line) end table.insert(self.currentError, messageContents) elseif messageType == "dig" then @@ -1094,7 +1130,7 @@ function DigshapeUI:buttonCallback_setCommand() ) end -function DigshapeUI:updateDigMode(mode,set) +function DigshapeUI:updateDigMode(mode, set) stdout("CAL", debug.getinfo(1, 'n').name or "@ line " .. debug.getinfo(1, 'S').linedefined) set = set == "set" and "set" or "clear" --In the button list, insert a > and < before and after the currently selected item. @@ -1123,15 +1159,15 @@ function DigshapeUI:updateDigMode(mode,set) end local doset - doset = function(label, kpen, text,set) + doset = function(label, kpen, text, set) if text == "<" then self.subviews.digmodeMenu.text_ids[label].key_pen = kpen end self.subviews.digmodeMenu.text_ids[label].pen = kpen - if set=="set" then + if set == "set" then self.subviews.digmodeMenu.text_ids[label].text = " "--text else - self.subviews.digmodeMenu.text_ids[label].text ="" + self.subviews.digmodeMenu.text_ids[label].text = "" end end @@ -1141,7 +1177,7 @@ end function DigshapeUI:buttonCallback_setDig(mode) stdout("CAL", debug.getinfo(1, 'n').name or "@ line " .. debug.getinfo(1, 'S').linedefined) - local buttonCallback_setDig_labelhelper + -- local buttonCallback_setDig_labelhelper @@ -1181,9 +1217,9 @@ function DigshapeUI:buttonCallback_argAdjust(argNum, argDir, argMod) local arg = self.activeCommand.args[argNum] stdout(">>>", "argadj---------------------", argNum, argDir, argMod) - printtable(self.activeCommand, "Arg Adj") + -- printtable(self.activeCommand, "Arg Adj") if arg == nil then - stdout("ERR","NIL ARG", argNum, argDir, argMod) + stdout("ERR", "NIL ARG", argNum, argDir, argMod) return end if argDir == "reset" then @@ -1232,9 +1268,8 @@ function DigshapeUI:buttonCallback_argAdjust(argNum, argDir, argMod) end end end - arg.currentValue = newvalue + arg.currentValue = shallowcopy(newvalue) stdout("MSG", "ArgAdj:" .. arg.name .. "=" .. tostring(arg.currentValue)) - printtable(self.activeCommand, "Arg Adj2") self:updateMenuDisplay() self:previewCurrentCommand() end @@ -1273,14 +1308,17 @@ function DigshapeUI:buttonCallback_showHelpPopup() end function DigshapeUI:buttonCallback_dualmove() ---move origin and cursor by same amount as current move key. + --move origin and cursor by same amount as current move key. --todo: code this. end - ---function DigshapeUI:buttonCallback_() --- ---end +function DigshapeUI:buttonCallback_floodAtCursor() + local digmode = self.activeDesgination + --if digmode == "M" then + -- digmode="d" + --end + self:runDigshapeCommand(("digshape lua flood 1000 " .. digmode)) +end --function DigshapeUI:buttonCallback_() -- @@ -1357,7 +1395,7 @@ function DigshapeUI:onRenderBody(dc) end self:renderOverlay() - dc:clear():seek(1, 1):pen(COLOR_WHITE):string("Digshape - " .. self.state) + dc:clear():seek(1, 1):pen(COLOR_WHITE):string("Digshape(" .. DigshapeUIversion .. ") - " .. self.state) end @@ -1369,7 +1407,7 @@ function DigshapeUI:onInput(keys) --TODO: deal with multi-key presses, because keys is an array of individuals. if df.global.cursor.x == -30000 then - stdout("ERR","Cursor offscreen, resetting.") + stdout("ERR", "Cursor offscreen, resetting.") local vp = self:getViewport() df.global.cursor = xyz2pos(math.floor((vp.x1 + math.abs((vp.x2 - vp.x1)) / 2) + .5), math.floor((vp.y1 + math.abs((vp.y2 - vp.y1) / 2)) + .5), vp.z) return From ee2c897c4a3caadf2ef2fcd7e4f8d51b8c707b1a Mon Sep 17 00:00:00 2001 From: Quatch Date: Fri, 2 Apr 2021 22:33:12 -0400 Subject: [PATCH 12/12] Hack of a command selector Added a ctrl+key selector for commands. No style. --- gui/digshape.lua | 91 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 85 insertions(+), 6 deletions(-) diff --git a/gui/digshape.lua b/gui/digshape.lua index 7c1c93e..41bc36e 100644 --- a/gui/digshape.lua +++ b/gui/digshape.lua @@ -8,7 +8,7 @@ gui front-end for digshape.rb, a geometric designations generating tool ]====] verbose = false --TODO: move these down later to be arguments -DigshapeUIversion = "20210402" +DigshapeUIversion = "20210402b" --dfhack.screen.invalidate() --force an immediate redraw. --TODO: use dfhack.print(args...) for better printing @@ -327,7 +327,7 @@ DigshapeUI.ATTRS { curve = { requireOrigin = true, requireMajor = true, requireZ = true, allowFilled = false, args = { { name = "Sharpness", required = true, default = { default = 1.5, min = 0, max = 100, inc = 0.1, type = "float" }, type = "float", desc = "How strongly the curve is pulled towards the cursor" } }, runSilent = false, digMode = "@", desc = "Draw a curve (bezier) from origin to major pulled towards cursor", aliases = { "cu", "b", "bezier", "bez", "bezeir" } }, --todo: allow filled. Also draw line, then fill shape --{ default = 1.5, min = 0, max = 100, inc = 0.1, type = "float" } --arc = { requireOrigin = true, requireMajor = false, requireZ = true, allowFilled = true, args = nil, runSilent = false,digMode="@",desc="Draw an arc from origin to major passing through cursor." }, - downstair = { requireOrigin = false, requireMajor = false, requireZ = false, allowFilled = false, args = { { name = "depth", required = true, default = 10, type = "int", desc = "Number of z levels down to designate." }, { name = "start", required = true, default = true, type = "bool", desc = "Should the starting level be updown [false] or down [true]" }, }, runSilent = false, digMode = "", desc = "Designate a 3x3 block of updown stairs, corners and center only", aliases = { "k", "ks", "stair", "down", "downstair", "downstairs", "stairs" } }, + downstair = { requireOrigin = false, requireMajor = false, requireZ = false, allowFilled = false, args = { { name = "depth", required = true, default = 10, type = "int", desc = "Number of z levels down to designate." }, { name = "start", required = true, default = true, type = "bool", desc = "Should the starting level be updown [false] or down [true]" }, }, runSilent = false, digMode = "", desc = "Designate a 3x3 block of updown stairs, corners and center only", aliases = { "k", "ks", "stair", "down", "downstairs", "stairs" } }, }, @@ -502,6 +502,63 @@ function DigshapeUI:init() }, }, + widgets.Label { + frame = { t = 24, l = 1 }, + view_id = "shapeModeMenu", + text = { + { text = "Choose shape: CTRL+..." }, + NEWLINE, + --{ text = "[" }, --Adjust: "},Designate: + { text = "", id = "label_shapemodeStart" }, + + { key = "CUSTOM_CTRL_I", text = "Spiral, ", key_sep = ":", + on_activate = self:callback('buttonCallback_setShape', 'i'), id = "button_shapemode_spiral", + }, + + { key = "CUSTOM_CTRL_C", text = "Circle, ", key_sep = ":", + on_activate = self:callback('buttonCallback_setShape', 'c'), id = "button_shapemode_circle", + }, + NEWLINE, + { key = "CUSTOM_CTRL_E", text = "Ellipse, ", key_sep = ":", + on_activate = self:callback('buttonCallback_setShape', 'e'), id = "button_shapemode_ellipse", + --pen=self.pens.digMode.selected,dpen=self.pens.digMode.deselected,enabled=false, + }, + { key = "CUSTOM_CTRL_P", text = "Polygon, ", key_sep = ":", + on_activate = self:callback('buttonCallback_setShape', 'p'), id = "button_shapemode_polygon", + -- pen=self.pens.digMode.selected,dpen=self.pens.digMode.deselected,enabled=false, + }, + NEWLINE, + { key = "CUSTOM_CTRL_L", text = "Line, ", key_sep = ":", + on_activate = self:callback('buttonCallback_setShape', 'l'), id = "button_shapemode_line", + -- pen=self.pens.digMode.selected,dpen=self.pens.digMode.deselected,enabled=false, + }, + { key = "CUSTOM_CTRL_T", text = "Star, ", key_sep = ":", + on_activate = self:callback('buttonCallback_setShape', 't'), id = "button_shapemode_star", + -- pen=self.pens.digMode.selected,dpen=self.pens.digMode.deselected,enabled=false, + }, + NEWLINE, + { key = "CUSTOM_CTRL_F", text = "Flood, ", key_sep = ":", + on_activate = self:callback('buttonCallback_setShape', 'f'), id = "button_shapemode_flood", + -- pen=self.pens.digMode.selected,dpen=self.pens.digMode.deselected,enabled=false, + }, + { key = "CUSTOM_CTRL_B", text = "Curve, ", key_sep = ":", + on_activate = self:callback('buttonCallback_setShape', 'b'), id = "button_shapemode_curve", + -- pen=self.pens.digMode.selected,dpen=self.pens.digMode.deselected,enabled=false, + }, + NEWLINE, + { key = "CUSTOM_CTRL_D", text = "Stair", key_sep = ":", + on_activate = self:callback('buttonCallback_setShape', 'd'), id = "button_shapemode_downstair", + -- pen=self.pens.digMode.selected,dpen=self.pens.digMode.deselected,enabled=false, + }, + --NEWLINE, + -- { text = "]" }, + + + + + }, + + }, widgets.Label { frame = { b = 1, l = 1 }, --place it inset one tile off the bottom left view_id = "bottomMenu", @@ -683,7 +740,7 @@ function DigshapeUI:updateMenuDisplay() --printtable(self.digshapeCommands[command]) if self.digshapeCommands[command].requireMajor then if self.major == nil then - print("enable alert major") + stdout("ERR","Ctrl pt A 'major' must be set for this command.") self:updateMenuArg("controlPointsMenu", "button_setCtrlA", { dpen = self.pens.alertMenu, disabled = false }) end --print("enable major") @@ -1320,9 +1377,31 @@ function DigshapeUI:buttonCallback_floodAtCursor() self:runDigshapeCommand(("digshape lua flood 1000 " .. digmode)) end ---function DigshapeUI:buttonCallback_() --- ---end +function DigshapeUI:buttonCallback_setShape(keypress) + stdout("CAL", debug.getinfo(1, 'n').name or "@ line " .. debug.getinfo(1, 'S').linedefined) + if keypress=="c" then + self:setCommand("circle") + elseif keypress=="e" then + self:setCommand("ellipse") + elseif keypress=="i" then + self:setCommand("spiral") + elseif keypress=="p" then + self:setCommand("polygon") + elseif keypress=="l" then + self:setCommand("line") + elseif keypress=="t" then + self:setCommand("star") + elseif keypress=="f" then + self:setCommand("flood") + elseif keypress=="b" then + self:setCommand("curve") + elseif keypress=="d" then + self:setCommand("downstair") + else + stdout("ERR","you can't get here.") + self:setCommand("spiral") + end +end --function DigshapeUI:buttonCallback_() --