1. This forum is obsolete and read-only. Feel free to contact us at support.keenswh.com

Sci-Fi Four Button Panel LCD

Discussion in 'Programming (In-game)' started by Jason Back, Jul 24, 2020.

  1. Jason Back

    Jason Back Trainee Engineer

    Messages:
    3
    Hi.
    I've searched for nearly an hour trying to find how to write text to the LCD panel on a button on the Sci-Fi Four Button Panel. I can't find how to do it in a script. The LCD panels are great for displaying what the buttons do but I want to provide some feedback on the action to the LCD screen. I've checked out the MDK API listings and echo'd all of the actions I can find but can't get any further with IMyButtonPanel.

    If anyone can point me in the right direction I would be grateful.
     
  2. oldman

    oldman Trainee Engineer

    Messages:
    8
    I don't have the Sci-Fi DLC, and in the MDK doc it just shows the Sci-Fi 4-button panel as an IMyButtonPanel... so either that doc is right and this will not work, or that doc is not displaying the full type info for this type of button panel and just maybe this will work... it is worth a shot I guess.

    I'm basing it on this guide here, where the author is just grabbing the drawing surface for the LCD off of the PG block by index:
    https://github.com/malware-dev/MDK-SE/wiki/Text-Panels-and-Drawing-Sprites#the-viewport

    The PG has the following LCDs shown in its control panel:
    * Large Display
    * Keyboard

    And the author grabs the Large Display by calling:
    Code:
    IMyTextSurface _drawingSurface = Me.GetSurface(0);
    In the above example "Me" is the PG, but in your case you want to instead use a reference to your Sci-Fi Four Button Panel LCD.

    IDK if the Sci-Fi Four Button Panel LCD has a similar listing of LCDs that you can just get at by index, but if so, then you can write text the same way as is shown in that page I linked at the beginning.
    So try something like this and if it doesn't work... well I tried:
    Code:
    var lcdButtonPanel = GridTerminalSystem.GetBlockWithName("<insert name here>") as IMyTextSurfaceProvider;
    IMyTextSurface drawingSurface = lcdButtonPanel.GetSurface(0);
    
    Echo ("I have something to draw on: " + (drawingSurface != null));
    // now do your drawing or whatever
    
    Or maybe the LCD is just not yet accessible via the PG?
     
  3. Jason Back

    Jason Back Trainee Engineer

    Messages:
    3

    Hi oldman
    Thanks a lot for taking the time to help me out. Lifetime software engineer (retired) but only a month old Space Engineer.

    I had seen that page before when playing with the programmable block but I'd only just started with SE and glazed over at it. I also had assumed that the IMyButtonPanel interface didn't have the desired functionality and I needed to get at the object behind it but failed to do so. I've been through malware's API listings and non of IMyTextSurfaceProvider inheritors is a panel but the cockpit and programmable block are. Is there another reference where you would find this information or is this simply something I should know as I learn?

    The key is the text surface, once you have that then job done. This may be obvious but IMyTextSurface.WriteString needs the LCD panel set to text and images but drawing to it requires the LCD panel set to Script.

    Thanks again
     
    • Like Like x 1
  4. oldman

    oldman Trainee Engineer

    Messages:
    8
    What my suggestion worked?! :) I was just guessing that it might have a similar, but undocumented access to the LCD like we have on the PG. I suppose if it works then we should make a PR to get the MDK doc updated to reflect that the sci-fi button panel implements IMyTextSurfaceProvider... I'll maybe come back to that once I am more familiar with what all this stuff means. My info is limited to probably what you've also seen in the MDK docs. I just started messing around with programming the PG myself yesterday, and when I sat down to see what I could do today, I figured it would be a good learning experience to try and figure out your question.

    It is so cool that SE has the PG block functionality, I really hope it doesn't get deprecated, as this just adds a lot of depth to what the game is to me (gracias @Malware).

    Seems this forum is not very active and people keep pointing to the discord server. But, I rather like forums, as they preserve history for all to browse and search and read (or maybe I have it wrong and discord does this... IDK I have only used it for voice comms when group gaming). If I keep playing around with the PG then I will probably visit here now and then when I have free time.

    Have fun!
     
  5. Jason Back

    Jason Back Trainee Engineer

    Messages:
    3
    Yes all working and I can now see status updates on the button's LCD panel. So much better as I don't like sending everything to a big LCD screen and the basic buttons I found I forgot which one did what !! It's more realistic to have the feedback local to the button.
    I have to agree on the forums and Discord. I'll check it out at some point maybe.

    A lot of the code examples I have seen are often depreciated or obsolete or access protected properties and methods so I've been struggling to find good references. It would be nice if the documentation was more comprehensive. I bought SE for less than a tenner so got the DLC to support when I became hooked.

    Cheers
     
  6. Jack H Talbot

    Jack H Talbot Trainee Engineer

    Messages:
    3
    Hello,

    I'm not a programmer of any kind but I've been fumbling my way through scripting with this website https://dco.pe/
    I also want to change the LCD on sci button panels and I think I know what to do but my question is do you know how I can change the textures of the screen and throw some text on it for a couple of seconds. the objective is when I push the button text appears on the screen with a black background, once the text is gone the texture reverts back to posters changing

    Could one of you please help me where would I put the var defining the sci fi panel


    IMyTextSurface _drawingSurface;
    RectangleF _viewport;

    // Script constructor
    public Program()
    {
    // Me is the programmable block which is running this script.
    // Retrieve the Large Display, which is the first surface
    _drawingSurface = Me.GetSurface(0);

    // Set the continuous update frequency of this script
    Runtime.UpdateFrequency = UpdateFrequency.Update100;

    // Calculate the viewport by centering the surface size onto the texture size
    _viewport = new RectangleF(
    (_drawingSurface.TextureSize - _drawingSurface.SurfaceSize) / 2f,
    _drawingSurface.SurfaceSize
    );

    // Make the text surface display sprites
    PrepareTextSurfaceForSprites(_drawingSurface);
    }

    // Main Entry Point
    public void Main(string argument, UpdateType updateType)
    {
    // Begin a new frame
    var frame = _drawingSurface.DrawFrame();

    // All sprites must be added to the frame here
    DrawSprites(ref frame);

    // We are done with the frame, send all the sprites to the text panel
    frame.Dispose();
    }

    // Drawing Sprites
    public void DrawSprites(ref MySpriteDrawFrame frame)
    {
    // Create background sprite
    var sprite = new MySprite()
    {
    Type = SpriteType.TEXTURE,
    Data = "Grid",
    Position = _viewport.Center,
    Size = _viewport.Size,
    Color = _drawingSurface.ScriptForegroundColor.Alpha(0.66f),
    Alignment = TextAlignment.CENTER
    };
    // Add the sprite to the frame
    frame.Add(sprite);

    // Set up the initial position - and remember to add our viewport offset
    var position = new Vector2(256, 20) + _viewport.Position;

    // Create our first line
    sprite = new MySprite()
    {
    Type = SpriteType.TEXT,
    Data = "Line 1",
    Position = position,
    RotationOrScale = 0.8f /* 80 % of the font's default size */,
    Color = Color.Red,
    Alignment = TextAlignment.CENTER /* Center the text on the position */,
    FontId = "White"
    };
    // Add the sprite to the frame
    frame.Add(sprite);

    // Move our position 20 pixels down in the viewport for the next line
    position += new Vector2(0, 20);

    // Here we add our clipping sprite. This is a simple rectangle. Nothing will be drawn outside it.
    // We create a rectangle that is covering the first half of the next line, cutting it off in the
    // middle.
    sprite = MySprite.CreateClipRect(new Rectangle(0, (int)position.Y - 16, (int)position.X, (int)position.Y + 16));
    // Add the sprite to the frame
    frame.Add(sprite);

    // Create our second line, we'll just reuse our previous sprite variable - this is not necessary, just
    // a simplification in this case.
    sprite = new MySprite()
    {
    Type = SpriteType.TEXT,
    Data = "Line 1",
    Position = position,
    RotationOrScale = 0.8f,
    Color = Color.Blue,
    Alignment = TextAlignment.CENTER,
    FontId = "White"
    };
    // Add the sprite to the frame
    frame.Add(sprite);
    }

    // Auto-setup text surface
    public void PrepareTextSurfaceForSprites(IMyTextSurface textSurface)
    {
    // Set the sprite display mode
    textSurface.ContentType = ContentType.SCRIPT;
    // Make sure no built-in script has been selected
    textSurface.Script = "";
    }
     
    Last edited: Aug 11, 2020
  7. oldman

    oldman Trainee Engineer

    Messages:
    8
    @Jack H Talbot, off the top of my head, IDK how to make the screen go back to its previous contents. But let's just take it one step at a time.

    I'd say step back from that example code that you have posted above and try starting with something simpler.

    Here's how you write something to your sci-fi button panel (make sure to name your button panel "The Button"):

    Code:
    public Program()
    {
    	Runtime.UpdateFrequency = UpdateFrequency.Update10;
    }
    
    public void Main(string argument, UpdateType updateType)
    {
    	IMyTextSurfaceProvider lcdButtonPanel = GridTerminalSystem.GetBlockWithName("The Button") as IMyTextSurfaceProvider;
    	if (lcdButtonPanel == null) {
    		Echo ("couldn't find the button panel");
    		return;
    	}
    
    	Echo ("found the button panel: " + lcdButtonPanel.ToString());
    
    	IMyTextSurface drawingSurface = lcdButtonPanel.GetSurface(0);
    
    	if (drawingSurface == null) {
    		Echo ("no drawing surface on the block?");
    		return;
    	}
    
    	Echo ("I have something to draw on");
    
    	drawingSurface.ContentType = ContentType.TEXT_AND_IMAGE;
    	drawingSurface.Alignment = VRage.Game.GUI.TextPanel.TextAlignment.CENTER;
    	drawingSurface.WriteText("hey it works!");
    }
    
    (I also pushed this example up to github here: https://github.com/aortez/hydrogen-candy/blob/master/write_to_lcd.cs)

    Note that how this is done is not efficient at all, but since you're new at this, I wouldn't worry about it too much. Instead I'd focus on just sort of figuring out how to do the basic things you want, and then later if you want to make things more efficient, I suggest reading over the discussion points from my earlier post here:
    https://forums.keenswh.com/threads/code-review-for-first-script.7404215/
    And then if you like you can post your script and see if anyone can help you with some pointers. But figure out how to get it working first.

    Next, I think you said that after you display the text for a while, you want to be able to able to go back to displaying whatever it was showing before... well I would like to help you with this, as I would like to know how to do this myself, but I don't have time to look into it currently. My guess is that you'll be saving away the state of the button panel LCD (drawingSurface from my example above), then writing whatever words you want to it, waiting a few seconds, then restoring the previous state of it.

    Good luck and don't get discouraged!
     
  8. Jack H Talbot

    Jack H Talbot Trainee Engineer

    Messages:
    3
    @oldman, Thank you so much. I didn't think anyone would help :D

    Is it ok if I ask you a couple questions, the article is helpful but I think I'm just missing a couple things to tie things together. At the bottom in the final code listing, lines 1 and 2 "IMyTextSurface _drawingSurface;" and "RectangleF _viewport;" why are these not in any {}? What part of the program are they and why...just all the questions about that? Then how do I tell what to put in the constructor part of the program? what's the difference between public main and public void and then they show this "public void GetFonts(List<System.String> fonts)" to get a list of available fonts but I don't get where to use that :D do I run it completely separately in visual studio code, do I have to then tell it to write the list and then where do I even write it, in the game or the console? :D I'm not sure where to start with my questions.

    Sorry to bombard you. thank you so much for your help so far. I think I can read your example and make sense of it. If I then had a separate "public void" and write something would it replace "hey it works" or will it keep updating one then the other?

    Also as far as changing it back I've "figured it out" but no elegantly. I would use one programmable block and run a program with the text on it then stop that one and run another "original" PB that will be showing the default imagery.

    I would prefer it all done in one, the structure would be something like

    Display "default" imagery if "timer block" not running is false

    if true display the text

    then the button just starts the timer block.

    I think that would be it but I'm not sure where to start and there's a lot of detail missing :D
     
  9. Jack H Talbot

    Jack H Talbot Trainee Engineer

    Messages:
    3
    hello again,

    Ive been taking som elessons and I think I understand most of the questions now,

    "
    drawingSurface.ContentType = ContentType.TEXT_AND_IMAGE;
    drawingSurface.Alignment = VRage.Game.GUI.TextPanel.TextAlignment.CENTER;
    drawingSurface.WriteText("hey it works!");

    " this part still confuses me as I can't figure out where drawingsurface is referenced before this unless the first line is initializing it?
     
  10. Malware

    Malware Master Engineer

    Messages:
    9,867
    IMyTextSurface drawingSurface = lcdButtonPanel.GetSurface(0);
     
    • Like Like x 1
  11. oldman

    oldman Trainee Engineer

    Messages:
    8
    Hi again @Jack H Talbot. Sounds like you are learning as you go. I remember back when I was a kid learning to program (just reading books at my local library), lots of things seemed confusing and I probably did everything all wrong, but I was still able to make cool stuff and have fun.

    BTW @Malware did answer your last question accurately.

    And I still wondered about how to answer your original question of (paraphrasing here): "how to show some text for a bit, then make it go back to showing whatever images were there previously?".

    So I looked at what I knew and then referred to the docs. I knew that since we had an IMyTextSurface in hand, I should probably start looking at the docs there and figure out if that was the right road to go down. Turns out it was.
    Here's the API doc for that type:
    https://github.com/malware-dev/MDK-SE/wiki/Sandbox.ModAPI.Ingame.IMyTextSurface

    I first saw that we have an accessor method: CurrentlyShownImage { get; } and thought that might be what I wanted... but then I looked further down that page and saw we could work with lists of image IDs and I recalled you saying "once the text is gone the texture reverts back to posters changing", so I am guessing you want a list of images and also I think just working with a list will work better in general, as it will work for both a single image and a list of images. And then I mucked about for a few minutes and came up with this script. It doesn't yet connect the button to triggering the text display, but hey one step at a time. Instead it just flips between the text and the previous textures on a fixed interval.

    Again this is terribly wasteful code, but I want to keep it simple and straight to the point:
    Code:
    /**
    *
    *
    */
    public Program()
    {
    	Runtime.UpdateFrequency = UpdateFrequency.Update10;
    }
    
    List<String> imageList = new List<String>();
    
    TimeSpan flipPeriod = new TimeSpan(0, 0, 5);
    TimeSpan timeSinceLastFlip = new TimeSpan(0, 0, 0);
    
    bool shouldShowText = false;
    
    public void Main(string argument, UpdateType updateType)
    {
    	timeSinceLastFlip += Runtime.TimeSinceLastRun;
    	Echo ("s: " + Runtime.LastRunTimeMs + ", timeSinceLastFlip: " + timeSinceLastFlip);
    	if (timeSinceLastFlip > flipPeriod) {
    		shouldShowText = !shouldShowText;
    		timeSinceLastFlip = new TimeSpan(0, 0, 0);
    	}
    
    	IMyTextSurfaceProvider lcdButtonPanel = GridTerminalSystem.GetBlockWithName("The Button") as IMyTextSurfaceProvider;
    	if (lcdButtonPanel == null) {
    		Echo ("couldn't find the button panel");
    		return;
    	}
    
    	IMyTextSurface drawingSurface = lcdButtonPanel.GetSurface(0);
    	if (drawingSurface == null) {
    		Echo ("no drawing surface on the block?");
    		return;
    	}
    
    	// Time to show the text.
    	if (shouldShowText) {
    		// If it is showing any image currently, copy the image IDs so we can switch
    		// back to showing them later.
    		if (!String.IsNullOrEmpty(drawingSurface.CurrentlyShownImage)) {
    			drawingSurface.GetSelectedImages(imageList);
    
    			// After we have the list of images, clear them so we can see our output
    			// text.
    			drawingSurface.ClearImagesFromSelection();
    		}
    		drawingSurface.ContentType = ContentType.TEXT_AND_IMAGE;
    		drawingSurface.Alignment = VRage.Game.GUI.TextPanel.TextAlignment.CENTER;
    		drawingSurface.WriteText("showing text");
    	} else {
    		// Show whatever was there previously...
    		drawingSurface.WriteText("");
    		drawingSurface.AddImagesToSelection(imageList, true);
    	}
    }
    
    https://github.com/aortez/hydrogen-candy/blob/master/write_to_lcd.cs

    Now finally before I bounce, I think you asked a great question here, so I would like to answer it:
    "At the bottom in the final code listing, lines 1 and 2 "IMyTextSurface _drawingSurface;" and "RectangleF _viewport;" why are these not in any {}? What part of the program are they and why..."

    In general we can refer to what lives inside of a set of curly braces as a scope. That stuff has it's own lifetime that expires once the program flow leaves that scope... those variables disappear. The first two lines in that listing however, are putting those variables into the global scope, so that they never just disappear until your recompile the script. Now keep in mind that my explanation here is very general... I think it is actually a little more complicated... like I think what we have here is something like a Class named Program and that "global scope" is actually member variables of an instance for the Program class, but I wouldn't worry terribly about that for now.

    I've been told this wiki is out of date, but it does have a good deal of introductory info, so I suggest reading it anyway. Down at the bottom of the page are some C# intro pages you might find handy.
    Here's the section that covers what I just explained above:
    https://spaceengineerswiki.com/Programming_Guide#Variable_lifetime_and_scoping

    Have fun!