Dashboard Plugin

Ett smörgåsbord av möjligheter, indeed :slight_smile:

I have added a circular progress bar in pure css. Not entirely complete yet as I have to account for dark/light themes but I'm getting closer and closer to my original vision from the first post above.

Don't forget that you can pack ring-in-a-ring if you run out of real estate. You might be able to group things together, in other words.

Working with svg was pleasant compared to css hacks:

I'm thinking about something like this for the temps:

I could add a plugin setting for each tool/bed like "safe temp: +/- 5 deg" and a "max expected temp". It would then be possible to mark anything below or above the target temp as "unsafe":

Would that be useful?

Great progress. I think the temp thing would be a good way to show the information with a variable setting for "safe zone relative to target". If you do that though you might consider some form of alarming mechanism when the temp is outside of those ranges. You would of course have to put some logic in while it was doing it's initial heat up to not alarm.

You could either attempt to make "good/acceptable" a vertically-oriented needle (similar to a guitar tuner) or you could go more like a typical car tachometer.

Yet another project for inspiration:

Thanks for the feedback. I'm currently leaning towards a simplistic approach similar to the circular progress bar. Something like this: https://codepen.io/davatron5000/pen/jzMmME
or This: https://www.mschweighauser.com/web-ui-for-a-weather-station/

I'll use three svg strokes. One fixed for the background, One for the set temperature over the background and a third for the current temp. Then I'll simply set the colour of the current temp stroke depending on if it is below or above the "safe range". The user can configure "max temp" (default to 300) and "safe range" (default: +/-5 degrees). Then I'll set the current temp stroke to blue if it is below the target temp, green if safe and red if above safe.

I'll try to create a mockup over the weekend.

That will look great. :+1:

Something like this:

https://codepen.io/stefancohen/pen/XWrapOR

The dark-gray path is the target temp.
The blue bar would go green once it goes over 195°C and red if it goes over 205°C.

To me, 170C is a "magic minimum" (PLA). I don't think Marlin will honor any extrusion/retraction commands lower than this temperature. You might do blue for the first part, green from there until the actual temperature.

Good point. A default "safe range" of +/-30 degrees (170-230) could achieve that.

I ran into multiple problems along the way but now I have the temp graphs in place.

There is still some styling to do but this feels like the right way to go.

2 Likes

Great progress @Stefan_Cohen. Couple of recommendations from a visual layout perspective.

  • move the print progress circle underneath Printing to put it inline with the temp graphs. I just saw your other post and see you made some changes already adding the fan speed as a circle.
  • make the gcode filename part of the "Printing" icon.

IMO these changes will make the best use of screen space and look a lot cleaner.

Thanks. Maybe I should remove the file name completelly and just center the progress. Or put the file name at the bottom (it may be really long). I usually remember what l’m printing, but there are times when it becomes a bit fuzzy :smiley:
I had to abandon the coloring of the temps for now. The SVG simply wouldn’t take my styling from the template. I’ll probably give it another try later.

I'll have to figure out how to add support for multiple languages down the road even if I only intend to release with English.

There are so far only a limited set of strings that should be available for translation:
"Dashboard"
"Connected"
"Disconnected"
"Fan Speed"
"Target"
"Layers"

I couldn't find anything in the plugin development documentation and there is only a brief explanation in translations/README.txt left by the cookiecutter. It looks like I need to encapsulate strings (marked with Jinja's _("...") or JavaScript's gettext("...")) but there is no explanation on how to get/set the locale or if that is done automatically. The wiki article doesn't go into details on how to deal with plugins.

General Overview

There are two ways: the OctoPrint way and the roll-your-own way. The OctoPrint way involves using the gettext() format in Python and as you've discovered and the Knockout version is _("...").

Even within the OctoPrint way there are two levels of scope: globally for OctoPrint and locally for your own plugin. Those gettext() calls create PO files for English (using Babel) and then you'd copy/rename them for other languages. This process can be done locally on your rig or on a website called Transifex. The end result would be to publish a language pack which works at the global level. Note that foosel herself maintains the German language pack. If you wanted to just insert some more strings into hers, you could consider submitting a PR. A good post to read might be this one.

Back to discussing local plugin scope now, it helps if you created your plugin using the cookiecutter approach. If so, then your plugin's setup.py includes the Babel-related commands for translating locally. (So it's good to be familiar with the global scope version from earlier.)

Granted, it feels to me that the documentation is sparse with respect to local scope plugin localisation. I'm guessing that the "OctoPrint way" version of this is to use gettext() for each string or substring then to run the appropriate Babel build command to create the PO files locally (as in python setup.py something). Then in theory, you'd clone the messages.pot or similar file into another subdirectory for another language, translate it locally in that file and it's then compiled in a later step.


I don't do all that. I've done a roll-your-own approach. Upon plugin startup, I read the contents of a system file to find out what their default language is. This wouldn't be a tutorial for how to do this; I'm just sharing my own solution.

        with open('/etc/default/locale') as l:
            for line in l.readlines():
                if line[:5] == 'LANG=':
                    self.mLanguage = line[5:].rstrip() # yields `en_US` perhaps

Since I'm using Kivy, I'm not interested in Knockout JS's way of putting this to the screen, I just need it in Python. So I create one yaml file per supported language_COUNTRY:

11%20AM

language.py:

import     os
import     yaml

class Language():
    def __init__(self, language):
        self.pack = None
        self.load_language(language)
        self.current_lang = 'en'

    def load_language(self, language):
        acceptable_languages = {
                                'en_CA': 'Language_Packs/en_CA.yaml',
                                'en_GB': 'Language_Packs/en_GB.yaml',
                                'en_US': 'Language_Packs/en_US.yaml',
                                'es_ES': 'Language_Packs/es_ES.yaml',
                                'es_MX': 'Language_Packs/es_MX.yaml',
                                'de_DE': 'Language_Packs/de_DE.yaml',
                                'fr_CA': 'Language_Packs/fr_CA.yaml',
                                'fr_FR': 'Language_Packs/fr_FR.yaml',
                                'it_IT': 'Language_Packs/it_IT.yaml'
                                }

        if language in acceptable_languages:
            _dir = os.path.dirname(__file__)
            yaml_path = os.path.join(_dir, acceptable_languages[language])
            with open(yaml_path, 'r') as file:
                self.pack = yaml.load(file, Loader=yaml.FullLoader)

            self.current_lang = language

    def reload_language(self, language):
        self.load_language(language)

Finally, in the Python code I might grab a translated string like this, noting that pack is a Kivy command:

        self.fileName =  op.kv_lang.pack['JobManagerScreen']['NoFileSelected']

Back to reality...

Think of those PO files as a text-based database. The key to find something is consistent across all the different folders which represent different languages. It's a simple concept. gettext receives a key, uses the default language from the system and then fetches one and only one string from the correct database.

1 Like

Keep in mind that you have serveral "text-sources", like Jinja2-Templates with Knockout.js, JavaScript and also sending text from the server to the web-ui via Python.

I collected some informations about I18N in my "KnowledgeBase (a.k.a scratchpad): https://github.com/OllisGit/OctoPrint-KnowledgeBase/wiki/I18N

So, it is always a good idea to think about a good software design of your plugin.

In my first plugin (https://github.com/OllisGit/OctoPrint-DisplayLayerProgress) I started with I18N-Support (German, Czech and English), but over the time I droped: de, cz....too much work!

Or I used the wrong approach, maybe a "custom solution" like @OutsourcedGuru is better.
@OutsourcedGuru do you a sample plugin where we can see how it is working?

There's nothing simple about it since it's a Kivy-based app, unfortunately. It's a nifty display interface for a 3D printer I'm working on.

Honestly, I find it actually fun everytime I need to add some new text to the interface. I'll jump into the Google Translate page and do the translations like this. As you can see from my session above with Visual Studio I've just modified all those. Ah, I've just added:

Common:
  StatusSerialError: "Serial error"

So now there's someplace else in the code which looks like:

    def update(self, dt):
        is_printing = op.op_printerState['state']['flags']['printing']
        is_paused = op.op_printerState['state']['flags']['paused']
        self.statusDetail = op.kv_lang.pack['Common']['StatusSerialError'] if self.bSerialOffline else op.op_printerState['state']['text']

It looks like this is run periodically to query the OP status, store it into some variables and then to create the localized text for the Status Bar at the top of the screen. In this context, a serial error would be trying to talk to a secondary control board (not to be confused with the printer controller board).

Thanks. I think I have a grasp on how it works now. I need to learn how to branch and merge anyway so I'll take this as an learning opportunity and do both. I've never used git before and it is a bit tricky.

I have spent some time trying to understand if/how it would be possible to get the per layer progress out of the GCode Viewer Tab. The gcode_command_slider is updated when "Sync with job progress" is enabled so it should be possible to add a Layer Progress indicator in the dashboard.

Do you have any ideas?

Ugh. That pull request probably failed.