Dashboard Plugin

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.

Following the Details link there is what you'd want to view to see why it failed.

I'll look into it ASAP, never have seen a failure mode like this so far. First need to take care of 1.3.12rc1 though :wink:

Thanks @foosel

It is probably a typo somewhere but I have been greping for "/plugins/dashboard/" in both the plugin source and the plugins.octoprint.org clone but I haven't found anything yet. I guess I have missed a step somewhere but I can't figure out what it is.

I actually suspect that Jekyll got confused by the duplicate entries in the YAML frontmatter (you have layout and id in there a bunch of times). But that's just a hunch so far.

Thanks. I just noticed that too. I will fix and try to issue a pull request update to see if it fixes things.

Update: Removing the duplicate entries in dashboard.md didn't resolve the issue. This may be something else.

I've gone over the instructions for registering a new plugin a couple of times now but I can't pinpoint any problems with the PR.

@foosel, do the images have to be embedded into the plugin repository? I've always done that when I've submitted PRs. You create a sub-folder in assets/img/plugins/ that matches your plugin_identifier and then link to those in your dashboard.md file.

They don't have to be in the repo for the build to work. It's just best practice because if some external site where stuff is hosted on goes down, it looks weird. And there's also a bit of GDPR insecurity.

1 Like

You could host the image on hackerz-R-us.com and then farm the logs for the image requests to get a list of public IP addresses which then could be tested for port-forwarding, for example.